如何强制新记录的列值仅来自 PostgreSQL 中的序列?

How to force new records to have column values ONLY from a sequence in PostgreSQL?

请参阅下面的答案和编辑#1。但是在PetaPoco/Npgsql.

下仍然触发失败

我在这里遗漏了一些基本的东西。我需要确保添加到 table 的 any 记录的顺序为 only。即使 orderno 由插入语句本身提供,这也应该包括在内。也就是说,

insert into returntooffice (chart_recid, returndate, torder, **orderno**) values (14982,'2016-11-09','2017-12-4 00:21:42.553508', **0**);

insert into returntooffice (chart_recid, returndate, torder) values (14982,'2016-11-09','2017-12-4 00:21:42.553508');

都应该从 序列中产生下一个 orderno 而不是 0 的 orderno。 也就是说,实际发生的是正在插入提供的 orderno(0)——而不是下一个序列值(8000)。我在这里使用触发器,因为实际的组合插入是由不遵守列上的 postgreSQL DEFAULT 子句的 ORM。

详情如下:

CREATE TABLE returntooffice
(
  recid serial NOT NULL,
  orderno integer NOT NULL,
  chart_recid integer NOT NULL,
  returndate date,
  torder timestamp without time zone NOT NULL DEFAULT now(),
  modified timestamp without time zone DEFAULT now(),
  return_as_needed boolean,
  is_deferred boolean,
  CONSTRAINT returntooffice_pk PRIMARY KEY (recid),
  CONSTRAINT returntooffice_chart_fk FOREIGN KEY (chart_recid)
      REFERENCES charts (recid) MATCH SIMPLE
      ON UPDATE CASCADE ON DELETE RESTRICT,
  CONSTRAINT returntooffice_order_unqiue UNIQUE (orderno),
  CONSTRAINT returntooffice_unqiue UNIQUE (chart_recid, torder)
)
WITH (
  OIDS=FALSE
);
ALTER TABLE returntooffice
  OWNER TO postgres;

CREATE TRIGGER get_next_order_number_trigger
  BEFORE INSERT
  ON returntooffice
  FOR EACH ROW
  EXECUTE PROCEDURE getnextorderno();

CREATE TRIGGER update_modified
  BEFORE UPDATE
  ON returntooffice
  FOR EACH ROW
  EXECUTE PROCEDURE update_modified();

CREATE SEQUENCE order_number_seq
  INCREMENT 1
  MINVALUE 1
  MAXVALUE 9223372036854775807
  START 6558
  CACHE 1;

ALTER TABLE order_number_seq
  OWNER TO postgres;

CREATE OR REPLACE FUNCTION getnextorderno()
  RETURNS trigger AS
$BODY$

BEGIN
   NEW.orderno := nextval('order_number_seq');
   Return NEW; 
END;

$BODY$
  LANGUAGE plpgsql VOLATILE
  COST 100;
ALTER FUNCTION getnextorderno()
  OWNER TO postgres;

编辑#1: 按照下面的建议重命名触发器,允许一切在 pgAdmin 下正常工作,但在 PetaPoco Insert 上仍然失败。有什么想法吗?

CREATE TRIGGER zzz_get_next_order_number_trigger
  BEFORE INSERT
  ON returntooffice
  FOR EACH ROW
  EXECUTE PROCEDURE getnextorderno();

您可能遇到其他问题,例如 table 上的另一个触发器也具有 BEFORE INSERT 的行为并在此触发器之后执行。请记住,相同行为的触发器执行顺序是按名称的字母顺序选择的。


我已经测试了你的情况。 (是的,在 Postgres 9.5.4 上,因为你声称它在这个版本中不起作用。) 一切正常。输入值每次都会被 order_number_seq.

中的下一个值覆盖

我们从序列的值 6558 开始,因此:

postgres=# insert into returntooffice (orderno, chart_recid) values (0, 1);
INSERT 0 1
postgres=# insert into returntooffice (chart_recid) values (2);
INSERT 0 1
postgres=# insert into returntooffice (orderno, chart_recid) values 
           (nextval('order_number_seq'::regclass), 3);
INSERT 0 1

预期的输出是:

postgres=# select recid, orderno, chart_recid from returntooffice;
 recid | orderno | chart_recid
-------+---------+-------------
     1 |    6558 |           1
     2 |    6559 |           2
     3 |    6561 |           3

为了重现 "issue" 我必须通过删除约束、替换不同语句的执行顺序并删除不必要的部分来修改创建脚本。这是:

CREATE TABLE returntooffice
(
  recid serial NOT NULL,
  orderno integer NOT NULL,
  chart_recid integer NOT NULL,
  returndate date,
  torder timestamp without time zone NOT NULL DEFAULT now(),
  modified timestamp without time zone DEFAULT now(),
  return_as_needed boolean,
  is_deferred boolean
)
WITH (
  OIDS=FALSE
);

CREATE OR REPLACE FUNCTION getnextorderno()
  RETURNS trigger AS
$BODY$

BEGIN
   NEW.orderno := nextval('order_number_seq');
   Return NEW; 
END;

$BODY$
  LANGUAGE plpgsql VOLATILE
  COST 100;

-- Trigger: get_next_order_number_trigger on returntooffice

-- DROP TRIGGER get_next_order_number_trigger ON returntooffice;

CREATE TRIGGER get_next_order_number_trigger
  BEFORE INSERT
  ON returntooffice
  FOR EACH ROW
  EXECUTE PROCEDURE getnextorderno();


CREATE SEQUENCE order_number_seq
  INCREMENT 1
  MINVALUE 1
  MAXVALUE 9223372036854775807
  START 6558
  CACHE 1;

杂乱触发器的两种替代方法。首先是操纵角色权限。

create table t (i serial, s text);

撤消相关角色对 table 的所有特权:

revoke all on t from test;

仅授予 select

grant select on t to test;

在除序列一之外的所有列上授予所有权限:

grant all (s) on t to test;

仅在序列上授予使用权:

revoke all on t_i_seq from test;
grant usage on t_i_seq to test;

现在该角色无法插入到序列列中:

insert into t (s) values ('a');
INSERT 0 1
insert into t (i,s) values (10,'a');
ERROR:  permission denied for relation t

第二种选择更简单。仅授予 select 角色:

revoke all on t from test;
grant select on t to test;

作为 table 所有者使用 'security definer' 创建一个插入函数:

create function f(_s text)
returns t as $$

    insert into t (s) values (_s)
    returning *;

$$ language sql security definer;

角色将只能使用函数插入。