Chỉ số duy nhất có thể bảo vệ trong postgres


14

Nhìn vào tài liệu postgres cho bảng thay đổi , có vẻ như các ràng buộc thông thường có thể được đánh dấu là DEFERRABLE(cụ thể hơn INITIALLY DEFERRED, đó là điều tôi quan tâm).

Các chỉ mục cũng có thể được liên kết với một ràng buộc, miễn là:

Chỉ mục không thể có các cột biểu thức cũng không phải là một chỉ mục một phần

Điều đó khiến tôi tin rằng hiện tại không có cách nào để có một chỉ mục duy nhất có điều kiện, như:

CREATE UNIQUE INDEX unique_booking
  ON public.booking
  USING btree
  (check_in, check_out)
  WHERE booking_status = 1;

INITIALLY DEFERREDnghĩa là, tính duy nhất 'ràng buộc' sẽ chỉ được xác minh khi kết thúc giao dịch (nếu SET CONSTRAINTS ALL DEFERRED;được sử dụng).

Giả định của tôi có đúng không, và nếu vậy, có cách nào để đạt được hành vi dự định không?

Cảm ơn

Câu trả lời:


15

Một chỉ mục không thể được hoãn lại - không quan trọng nếu nó có UNIQUEhay không, một phần hay không, chỉ là một UNIQUEràng buộc. Các loại hạn chế ( FOREIGN KEY, PRIMARY KEY, EXCLUDE) cũng deferrable - nhưng không CHECKràng buộc.

Vì vậy, chỉ mục một phần duy nhất (và ràng buộc ngầm mà nó thực hiện) sẽ được kiểm tra tại mỗi câu lệnh (và trên thực tế sau mỗi lần chèn / cập nhật hàng trong triển khai hiện tại), không phải vào cuối giao dịch.


Những gì bạn có thể làm, nếu bạn muốn thực hiện ràng buộc này là có thể bảo vệ được, là thêm một bảng nữa trong thiết kế. Một cái gì đó như thế này:

CREATE TABLE public.booking_status
  ( booking_id int NOT NULL,               -- same types
    check_in timestamp NOT NULL,           -- as in  
    check_out timestamp NOT NULL,          -- booking
    CONSTRAINT unique_booking
        UNIQUE (check_in, check_out)
        DEFERRABLE INITIALLY DEFERRED,
    CONSTRAINT unique_booking_fk
        FOREIGN KEY (booking_id, check_in, check_out)
        REFERENCES public.booking (booking_id, check_in, check_out)
        DEFERRABLE INITIALLY DEFERRED
  ) ;

Với thiết kế này và giả sử booking_statuschỉ có 2 tùy chọn có thể (0 và 1), bạn có thể xóa hoàn toàn khỏi booking(nếu có một hàng tại booking_status, đó là 1, nếu không là 0).


Một cách khác là (ab) sử dụng một EXCLUDEràng buộc:

ALTER TABLE booking
    ADD CONSTRAINT unique_booking
        EXCLUDE 
          ( check_in  WITH =, 
            check_out WITH =, 
            (CASE WHEN booking_status = 1 THEN TRUE END) WITH =
          ) 
        DEFERRABLE INITIALLY DEFERRED ;

Đã thử nghiệm tại dbfiddle .

Những gì ở trên làm:

  • Các CASEbiểu hiện trở NULLkhi booking_statuslà null hoặc khác với 1. Chúng ta có thể viết (CASE WHEN booking_status = 1 THEN TRUE END)như (booking_status = 1 OR NULL)nếu mà làm cho nó bất kỳ rõ ràng hơn.

  • Các ràng buộc duy nhất và loại trừ chấp nhận các hàng trong đó một hoặc nhiều biểu thức là NULL. Vì vậy, nó hoạt động như một chỉ mục được lọc với WHERE booking_status = 1.

  • Tất cả các WITHtoán tử là =do nó hoạt động như một UNIQUEràng buộc.

  • Hai kết hợp này làm cho ràng buộc hoạt động như một chỉ mục duy nhất được lọc.

  • Nhưng đó là một hạn chế và các EXCLUDEràng buộc có thể được hoãn lại.


2
+1 cho phiên bản EXCLUDE, là những gì tôi cần. Dưới đây là một ví dụ khác cho thấy các khả năng của EXCLUDE: cybertec-postgresql.com/en/postgresql-exclude-beyond-unique
Benjamin Peter

(CASE WHEN booking_status = 1 THEN TRUE END) WITH =)nên được thay thế bằng ) WHERE (booking_status = 1)vì "Các ràng buộc loại trừ được triển khai bằng cách sử dụng một chỉ mục" và chỉ mục một phần này WHEREsẽ nhỏ hơn và nhanh hơn - postgresql.org/docs/cản/sql-createtable.htmlpostgresql.org/docs/cản/sql- createdindex.html
Denis Ryzhkov

1

Mặc dù nhiều năm của câu hỏi này đã trôi qua, tôi muốn làm rõ cho những người nói tiếng Tây Ban Nha, các bài kiểm tra đã được thực hiện trong Postgres:

Các ràng buộc sau đây đã được thêm vào một bảng gồm 1337 bản ghi, trong đó bộ này là khóa chính:

**Bloque 1**
ALTER TABLE ele_kitscompletos
ADD CONSTRAINT unique_div_nkit
PRIMARY KEY (div_nkit) 

Điều này tạo ra một khóa chính mặc định KHÔNG ĐƯỢC ĐÁNH GIÁ cho bảng vì vậy khi thử CẬP NHẬT tiếp theo, chúng tôi gặp lỗi:

update ele_kitscompletos
set div_nkit = div_nkit + 1; 

LRI: khóa trùng lặp vi phạm hạn chế duy nhất «unique_div_nkit»

Trong Postgres, việc thực hiện CẬP NHẬT cho mỗi ROW xác minh rằng RESTRICTION hoặc CONSTRAINT được đáp ứng.


CONSTRAINT IMMEDIATE hiện được tạo và mỗi câu lệnh được thực thi riêng:

ALTER TABLE ele_kitscompletos
ADD CONSTRAINT unique_div_nkit
PRIMARY KEY (div_nkit)
DEFERRABLE INITIALLY IMMEDIATE

**Bloque 2**
BEGIN;   
UPDATE ele_kitscompletos set div_nkit = div_nkit + 1;
INSERT INTO public.ele_kitscompletos(div_nkit, otro_campo)
VALUES 
  (1338, '888150502');
COMMIT;

Truy vấn OK, 0 hàng bị ảnh hưởng (thời gian thực hiện: 0 ms; tổng thời gian: 0 ms) Truy vấn OK, 1328 hàng bị ảnh hưởng (thời gian thực hiện: 858 ms; tổng thời gian: 858 ms) ERROR: llave trùngada viola giới hạn : Ya tồn tại la llave (div_nkit) = (1338).

Ở đây SI cho phép thay đổi khóa chính vì nó thực thi toàn bộ câu hoàn chỉnh đầu tiên (1328 hàng); nhưng mặc dù là trong giao dịch (BEGIN), CONSTRAINT được xác thực ngay lập tức sau khi hoàn thành mỗi câu mà không thực hiện CAM KẾT, do đó tạo ra lỗi khi thực hiện INSERT. Cuối cùng, chúng tôi đã tạo ra CONSTRAINT DEAXRED làm như sau:

**Bloque 3**
ALTER TABLE public.ele_edivipol
DROP CONSTRAINT unique_div_nkit RESTRICT;   

ALTER TABLE ele_edivipol
ADD CONSTRAINT unique_div_nkit
PRIMARY KEY (div_nkit)
DEFERRABLE INITIALLY DEFERRED

Nếu chúng tôi thực thi từng câu lệnh của ** Khối 2 **, thì mỗi câu riêng biệt, không có lỗi nào được tạo ra cho INSERT vì nó không xác thực nhưng CAM KẾT cuối cùng được thực thi khi tìm thấy sự không nhất quán.


Để biết thông tin đầy đủ bằng tiếng Anh, tôi khuyên bạn nên kiểm tra các liên kết:

Các ràng buộc SQL có thể bảo vệ trong Độ sâu

KHÔNG ĐÁNG TIN CẬY so với NGAY LẬP TỨC NGAY LẬP TỨC

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.