Một giải pháp khả thi là tạo bảng phụ chứa tiền tố khóa của bạn, sau đó sử dụng kết hợp các ràng buộc duy nhất và loại trừ với trình kích hoạt chèn để thực thi ngữ nghĩa duy nhất mà bạn muốn. Ở mức cao, cách tiếp cận này chia nhỏ mỗi khóa thành danh sách tiền tố và áp dụng điều gì đó tương tự như ngữ nghĩa khóa của người đọc: bất kỳ số nào có thể chia sẻ tiền tố miễn là không có khóa nào = tiếp đầu ngữ. Để thực hiện điều đó, danh sách các tiền tố bao gồm chính khóa đó với một lá cờ đánh dấu nó là tiền tố đầu cuối.
Bảng phụ trông như thế này. Chúng tôi sử dụng một số CHAR
thay vì BOOLEAN
cho cờ vì sau này chúng tôi sẽ thêm một ràng buộc không hoạt động trên các cột boolean.
CREATE TABLE prefixes (
id INTEGER NOT NULL,
prefix TEXT NOT NULL,
is_terminal CHAR NOT NULL,
CONSTRAINT prefixes_id_fk
FOREIGN KEY (id)
REFERENCES your_table (id)
ON DELETE CASCADE,
CONSTRAINT prefixes_is_terminal
CHECK (is_terminal IN ('t', 'f'))
);
Bây giờ chúng ta sẽ cần phải xác định một kích hoạt trên chèn vào your_table
cũng để chèn hàng vào prefixes
, chẳng hạn rằng
INSERT INTO your_table (id, key) VALUES (1, ‘abc');
gây
INSERT INTO prefixes (id, prefix, is_terminal) VALUES (1, 'a', ‘f’);
INSERT INTO prefixes (id, prefix, is_terminal) VALUES (1, 'ab', ‘f’);
INSERT INTO prefixes (id, prefix, is_terminal) VALUES (1, 'abc', ’t’);
Chức năng kích hoạt có thể trông giống như điều này. Tôi chỉ bao gồm trường hợp INSERT
ở đây, nhưng chức năng có thể được thực hiện để xử lý UPDATE
cũng bằng cách xóa các tiền tố cũ và sau đó chèn các tiền tố mới. Trường hợp DELETE
được bao gồm bởi ràng buộc khóa ngoài khóa trên prefixes
.
CREATE OR REPLACE FUNCTION insert_prefixes() RETURNS TRIGGER AS $$
DECLARE
is_terminal CHAR := 't';
remaining_text TEXT := NEW.key;
BEGIN
LOOP
IF LENGTH(remaining_text) <= 0 THEN
EXIT;
END IF;
INSERT INTO prefixes (id, prefix, is_terminal)
VALUES (NEW.id, remaining_text, is_terminal);
is_terminal := 'f';
remaining_text := LEFT(remaining_text, -1);
END LOOP;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
Chúng tôi thêm hàm này vào bảng làm trình kích hoạt theo cách thông thường.
CREATE TRIGGER insert_prefixes
AFTER INSERT ON your_table
FOR EACH ROW
EXECUTE PROCEDURE insert_prefixes();
Một hạn chế loại trừ và một chỉ số duy nhất một phần sẽ thi hành mà liên tiếp nơi is_terminal = ’t’
không thể va chạm với một hàng tiền tố như nhau bất kể giá trị is_terminal
của nó, và rằng chỉ có một hàng với is_terminal = ’t’
:
ALTER TABLE prefixes ADD CONSTRAINT prefixes_forbid_conflicts
EXCLUDE USING gist (prefix WITH =, is_terminal WITH <>);
CREATE UNIQUE INDEX ON prefixes (prefix) WHERE is_terminal = 't';
Điều này cho phép các hàng mới không xung đột nhưng ngăn những hàng xung đột, bao gồm trong INSERT nhiều hàng.
db=# INSERT INTO your_table (id, key) VALUES (1, 'a.b.c');
INSERT 0 1
db=# INSERT INTO your_table (id, key) VALUES (2, 'a.b.b');
INSERT 0 1
db=# INSERT INTO your_table (id, key) VALUES (3, 'a.b');
ERROR: conflicting key value violates exclusion constraint "prefixes_forbid_conflicts"
db=# INSERT INTO your_table (id, key) VALUES (4, 'a.b.c');
ERROR: duplicate key value violates unique constraint "prefixes_prefix_idx"
db=# INSERT INTO your_table (id, key) VALUES (5, 'a.b.c.d');
ERROR: conflicting key value violates exclusion constraint "prefixes_forbid_conflicts"
db=# INSERT INTO your_table (id, key) VALUES (6, 'a.b.d'), (7, 'a');
ERROR: conflicting key value violates exclusion constraint "prefixes_forbid_conflicts"
Tôi đã đi theo cách này, sau đó tôi phát hiện ra công ty Postgres rất cũ, không hỗ trợ các ràng buộc 'EXCLUDE'! Hàm bạn muốn trông đơn giản như vị trí ($ 1 trong $ 2)> 0 hoặc vị trí ($ 2 trong $ 1)> 0. –
Bảng có thể có nhiều bản ghi. Cách tiếp cận này có thể sử dụng bất kỳ chỉ mục nào không? –
@Juraj có, tính năng EXCLUDE này luôn đòi hỏi một chỉ mục, do đó ràng buộc là khá nhanh. BTW - giải pháp hiện đã hoàn thành nên hãy kiểm tra nó (nên làm việc trên 9.1+) – filiprem