postgres - 在事务提交之前触发

发布于 2024-11-24 14:48:43 字数 379 浏览 0 评论 0原文

我想知道是否可以间接地在事务即将提交之前执行触发器?在此触发器中,我将进行一致性检查并在需要时回滚事务。

例如,我有三个表:

users (id, name)
groups (id, name)
user_in_group (user_id, group_id)

我想创建一个触发器来验证用户始终是组的一部分。不允许孤儿用户。每次发生对 users 的插入时,此触发器都会验证是否也发生了对 user_in_group 的相应插入。如果不是,事务将不会提交。

使用简单的基于行或基于语句的触发器无法完成此操作,因为上述场景需要两个单独的语句。

相反,当从 user_in_group 发生删除时,可以通过基于行的触发器轻松完成。

I was wondering if it is indirectly possible to have a trigger executed just before the transaction is about to commit? In this trigger, I will do consistency checks and rollback the transaction if required.

For example, I have three tables:

users (id, name)
groups (id, name)
user_in_group (user_id, group_id)

I would like to create a trigger which verifies that a user is always part of a group. No orphan users are allowed. Each time an insert into users occurs, this trigger will verify that a correspondering insert into user_in_group also occured. If not, the transaction will not commit.

This cannot be done using a simple row- or statement- based trigger, since the above scenario requires two separate statements.

The other way around, when a delete from user_in_group happens, can be easily done by a row-based trigger.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

花间憩 2024-12-01 14:48:43

您是否查看了 CREATE CONSTRAINT TRIGGER,其中 DEFERRABLE (INITIALLY DEFERRED) 选项?

Did you look into the CREATE CONSTRAINT TRIGGER, with the DEFERRABLE (INITIALLY DEFERRED) option?

撕心裂肺的伤痛 2024-12-01 14:48:43

正确的方法难道不是在数据库中建立约束吗?这似乎正是约束的用途。添加一个外键约束和一个非空约束,看起来你应该开始做生意了。

现在针对对称约束进行了修改:

drop table foousers cascade;
drop table foogroups cascade;
drop table foousergrps cascade;
create table foousers (id int primary key, name text);
create table foogroups  (id int primary key, name text);
create table foousergrps (user_id int unique references foousers not null, group_id int unique references foogroups not null);
alter table foogroups add foreign key (id) references foousergrps (group_id) deferrable initially deferred;
alter table foousers add foreign key (id) references foousergrps (user_id) deferrable initially deferred;
begin;
insert into foousers values (0, 'root');
insert into foousers values (1, 'daemon');
insert into foogroups values (0, 'wheel');
insert into foogroups values (1, 'daemon');
insert into foousergrps values (0,0);
insert into foousergrps values (1,1);
commit;

禁止:

insert into foousers values (2, 'bad');
insert into foousergrps values (2,2);

(non-deferable, boo) 检查函数示例:

create table foousergrps (user_id int unique references foousers not null, group_id int not null);
create function fooorphangroupcheck(int) returns boolean as $
declare
  gid alias for $1;
begin
  perform 1 from foousergrps where group_id = gid limit 1;
  if NOT FOUND then return false;
  end if;
  return true;
end;
$
LANGUAGE 'plpgsql';
alter table foogroups add check (fooorphangroupcheck(id));

Wouldn't the correct method really be to establish constraints into the database? That seems exactly what constraints are for. Add a foreign key constraint and a not null and it would seem you should be in business.

Now revised for symmetrical constraints:

drop table foousers cascade;
drop table foogroups cascade;
drop table foousergrps cascade;
create table foousers (id int primary key, name text);
create table foogroups  (id int primary key, name text);
create table foousergrps (user_id int unique references foousers not null, group_id int unique references foogroups not null);
alter table foogroups add foreign key (id) references foousergrps (group_id) deferrable initially deferred;
alter table foousers add foreign key (id) references foousergrps (user_id) deferrable initially deferred;
begin;
insert into foousers values (0, 'root');
insert into foousers values (1, 'daemon');
insert into foogroups values (0, 'wheel');
insert into foogroups values (1, 'daemon');
insert into foousergrps values (0,0);
insert into foousergrps values (1,1);
commit;

Forbidden:

insert into foousers values (2, 'bad');
insert into foousergrps values (2,2);

Example of (non-deferrable, boo) check function:

create table foousergrps (user_id int unique references foousers not null, group_id int not null);
create function fooorphangroupcheck(int) returns boolean as $
declare
  gid alias for $1;
begin
  perform 1 from foousergrps where group_id = gid limit 1;
  if NOT FOUND then return false;
  end if;
  return true;
end;
$
LANGUAGE 'plpgsql';
alter table foogroups add check (fooorphangroupcheck(id));
轮廓§ 2024-12-01 14:48:43

查看文档,似乎没有这样的触发选项...因此实现“无孤儿用户”规则的一种方法是不允许直接插入 usersuser_in_group代码>表。而是创建一个带有 更新规则,将数据插入到正确的表中。

或者只允许通过存储过程插入新用户,该存储过程将所有必需的数据作为输入,因此不允许没有组的用户。

顺便说一句,为什么你有单独的用户和组关系表?为什么不将 group_id 字段添加到具有 FK / NOT NULL 约束的 users 表中?

Lookind at the doc, there seems to be no such trigger option... so one way to achieve "no orphan users" rule would be to not allow direct insert into users and user_in_group tables. Instead create a view (which combines these tables, ie user_id, user_name, group_id) with a update rule which inserts data into right tables.

Or only allow inserting new users via stored procedure which takes all required data as inpud and thus doesn't allow users withoud group.

BTW, why do you have separate table for user and group relationship? Why not add group_id field into users table with FK / NOT NULL constraint?

毁梦 2024-12-01 14:48:43

来自文档。 。 。

触发器可以定义为在任何 INSERT 之前或之后执行,
UPDATE 或 DELETE 操作,每个修改行一次,或者每个修改行一次
SQL语句。

From the docs . . .

Triggers can be defined to execute either before or after any INSERT,
UPDATE, or DELETE operation, either once per modified row, or once per
SQL statement.

一梦等七年七年为一梦 2024-12-01 14:48:43

您可以使用 sql WITH 运算符,如下所示:

WITH insert_user AS (
  INSERT INTO users(name) VALUES ('bla-bla-user') RETURNING id
)
INSERT INTO user_in_group(user_id, group_id) 
  SELECT id, 999 FROM insert_user UNION
  SELECT id, 888 FROM insert_user;

SELECT groups (id, name) user_in_group (user_id, group_id)

You can use the sql WITH operator, like this:

WITH insert_user AS (
  INSERT INTO users(name) VALUES ('bla-bla-user') RETURNING id
)
INSERT INTO user_in_group(user_id, group_id) 
  SELECT id, 999 FROM insert_user UNION
  SELECT id, 888 FROM insert_user;

SELECT groups (id, name) user_in_group (user_id, group_id)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文