2011-07-19 46 views
8

我想知道在事務即將提交之前是否可以間接執行觸發器?在這個觸發器中,我將進行一致性檢查並在需要時回滾事務。postgres - 在事務提交前觸發

例如,我有三個表:

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

我想創建它驗證用戶始終是一個組的一部分的觸發器。不允許孤兒用戶。每次發生用戶插入時,此觸發器都會驗證是否也發生了到user_in_group中的相應插入操作。如果不是,交易將不會提交。

這不能使用簡單的基於行或語句的觸發器來完成,因爲上述場景需要兩個單獨的語句。

另一種方式是,當從user_in_group中刪除時,可以通過基於行的觸發器輕鬆完成。

回答

0

the docs。 。 。

觸發器可以定義之前或任何INSERT之後執行, UPDATE或DELETE操作,每修改行任一一次,或每 SQL語句一次。

+0

我知道。使用這些類型的觸發器無法完成問題中描述的場景。這就是我尋找間接方式的原因。你碰巧知道嗎? –

1

Lookind的文檔,似乎沒有這樣的觸發選項...這樣一個實現方式「無孤兒用戶」的規則將不允許直接INSERT INTO usersuser_in_group表。相反,創建一個視圖(它將這些表,即user_id, user_name, group_id)與一個update rule,它將數據插入到右表中。

或者只允許通過存儲過程插入新用戶,該存儲過程將所有需要的數據作爲inpud使用,因此不允許用戶使用組。

順便說一句,爲什麼你有單獨的用戶和組關係表?爲什麼不添加group_id字段到users表中有FK/NOT NULL約束?

+0

查看更新規則是很酷的想法,謝謝!關於你的其他建議,這是如何工作的?在程序之外執行查詢有什麼區別? –

+0

SP的想法是將兩個插入操作合併到一個命令中 - 如果它在任何時候都失敗(即,您已經插入'users'表中但未插入到'user_group'中),所有操作都將回滾並且不會結束與孤兒記錄。 – ain

+0

啊好的。但通過僅使用SP,我無法阻止用戶直接訪問表格? –

1

難道真正的方法是建立數據庫的約束嗎?這似乎恰恰是約束條件。添加一個外鍵約束和一個非空的,它似乎你應該在業務。

現在修爲對稱的約束:

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); 

的(不可延遲,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)); 
+0

請再次閱讀說明。這是關於插入用戶而不插入user_in_group。不是關於從user_in_group中刪除或使用無效的引用。 –

+0

@Appelsien S:請再讀一遍我的答案。我在添加示例後立即指出,我的約束工作方式是錯誤的,現在已經修復它,以便孤兒用戶不被允許(雖然仍然禁止糟糕的user_in_group條目 - 不禁止這些會更簡單,因爲沒有創建表更改表將是必需的)。 –

+0

在修訂後的版本中,foousergrps中的user_id和group_id是唯一的。這當然不是理想的:foousergrps是一個N對N的關係。你做到了1-1的關係。 –

15

你看進CREATE CONSTRAINT TRIGGER,與DEFERRABLE (INITIALLY DEFERRED)選項?

+0

+1,以及對外鍵,唯一鍵和排除約束使用相同的「可延遲初始延遲」。 –

+0

謝謝。我沒有意識到可以在約束觸發器上設置DEFERRABLE。這最好地解決了我的問題。 –

+1

如果他們有這個工作,爲什麼他們不允許CHECK()約束延遲?那麼,好的答案。 –

0

您可以使用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)