2011-07-19 26 views
8

Tôi đã tự hỏi nếu nó là gián tiếp có thể có một kích hoạt thực hiện ngay trước khi giao dịch sắp cam kết? Trong trình kích hoạt này, tôi sẽ thực hiện kiểm tra tính nhất quán và khôi phục giao dịch nếu cần.postgres - kích hoạt trước khi giao dịch cam kết

Ví dụ, tôi có ba bảng:

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

Tôi muốn tạo ra một kích hoạt mà xác nhận rằng một người sử dụng luôn luôn là một phần của một nhóm. Không cho phép người dùng mồ côi. Mỗi lần chèn vào người dùng xảy ra, trình kích hoạt này sẽ xác minh rằng một chèn tương ứng vào user_in_group cũng xảy ra. Nếu không, giao dịch sẽ không được thực hiện.

Điều này không thể thực hiện bằng cách sử dụng trình kích hoạt dựa trên hàng hoặc lệnh đơn giản vì kịch bản trên yêu cầu hai câu lệnh riêng biệt.

Cách khác, khi xóa từ user_in_group xảy ra, có thể dễ dàng thực hiện bởi trình kích hoạt dựa trên hàng.

Trả lời

0

Từ the docs. . .

Gây ra có thể được xác định để thực hiện trước hoặc sau khi bất kỳ INSERT, UPDATE, hoặc DELETE hoạt động, hoặc một lần cho mỗi hàng sửa đổi, hoặc một lần mỗi câu lệnh SQL.

+0

Tôi biết. Không thể hoàn thành kịch bản được mô tả trong câu hỏi bằng cách sử dụng các loại trình kích hoạt này. Đó là lý do tại sao tôi đang tìm kiếm một cách gián tiếp. Bạn có biết ai không? –

1

lookind tại doc, có vẻ là không có tùy chọn kích hoạt như vậy ... vì vậy một cách để đạt được "không có người sử dụng trẻ mồ côi" quy tắc sẽ không cho phép chèn trực tiếp vào usersuser_in_group bảng. Thay vào đó, hãy tạo chế độ xem (kết hợp các bảng này, tức là user_id, user_name, group_id) với một số update rule chèn dữ liệu vào bảng bên phải.

Hoặc chỉ cho phép chèn người dùng mới thông qua quy trình được lưu trữ có tất cả dữ liệu bắt buộc dưới dạng inpud và do đó không cho phép người dùng có nhóm.

BTW, tại sao bạn có bảng riêng cho mối quan hệ người dùng và nhóm? Tại sao không thêm trường group_id vào users bảng với FK/NOT NULL ràng buộc?

+0

Xem với quy tắc cập nhật là ý tưởng hay, cảm ơn! Về đề xuất khác của bạn, làm thế nào mà có thể làm việc? Sự khác biệt với việc thực hiện các truy vấn bên ngoài một quy trình là gì? –

+0

Ý tưởng của SP là kết hợp cả hai chèn vào lệnh đơn - nó sẽ thất bại tại bất kỳ điểm nào (tức là bạn đã chèn vào bảng 'users' nhưng không vào' user_group') tất cả các hành động sẽ được khôi phục và bạn sẽ không kết thúc với hồ sơ trẻ mồ côi. – ain

+0

Ah alright. Nhưng chỉ bằng cách sử dụng SP, tôi không thể ngăn người dùng truy cập trực tiếp vào bảng? –

1

Phương pháp đúng có thực sự là thiết lập các ràng buộc trong cơ sở dữ liệu không? Điều đó có vẻ chính xác những gì là khó khăn cho. Thêm một ràng buộc khoá ngoại và không phải là rỗng và có vẻ như bạn nên ở trong kinh doanh.

Bây giờ sửa đổi cho trở ngại đối xứng:

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

Ví dụ về (không deferrable, boo) chức năng kiểm tra:

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

Vui lòng đọc lại mô tả. Đó là về cách chèn người dùng mà không cần chèn nó vào user_in_group. Không phải về việc xóa khỏi user_in_group hoặc sử dụng các tham chiếu không hợp lệ. –

+0

@Appelsien S: Vui lòng đọc lại câu trả lời của tôi. Tôi đã chỉ ra ngay sau khi tôi thêm ví dụ rằng ràng buộc của tôi đã hoạt động sai và giờ đã sửa nó để người dùng mồ côi không được phép (trong khi vẫn cấm các mục nhập user_in_group xấu - không cấm những người sẽ đơn giản hơn để tạo các bảng vì không có thay đổi bảng sẽ được yêu cầu). –

+0

Trong phiên bản sửa đổi của bạn, user_id và group_id trong foousergrps được thực hiện duy nhất. Đây là ofcourse không mong muốn: foousergrps là một N-to-N quan hệ. Bạn đã làm cho nó một mối quan hệ 1-1. –

15

Cậu nhìn vào CREATE CONSTRAINT TRIGGER, với tùy chọn DEFERRABLE (INITIALLY DEFERRED)?

+0

+1 và cùng một 'trì hoãn ban đầu trì hoãn 'cho khóa ngoại, hạn chế và loại trừ ràng buộc. –

+0

Cảm ơn. Tôi đã không nhận ra rằng DEFERRABLE có thể được đặt trên các trigger gây ra. Điều này giải quyết câu hỏi của tôi tốt nhất tôi nghĩ. –

+1

Nếu họ có điều này làm việc, tại sao trên thế giới họ không cho phép CHECK() hạn chế để được trì hoãn? Vâng, câu trả lời tốt. –

0

Bạn có thể sử dụng WITH hành sql, như thế này:

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) 
Các vấn đề liên quan