Ràng buộc duy nhất nhiều cột PostgreSQL và các giá trị NULL


94

Tôi có một bảng như sau:

create table my_table (
    id   int8 not null,
    id_A int8 not null,
    id_B int8 not null,
    id_C int8 null,
    constraint pk_my_table primary key (id),
    constraint u_constrainte unique (id_A, id_B, id_C)
);

Và tôi muốn (id_A, id_B, id_C)khác biệt trong mọi tình huống. Vì vậy, hai chèn sau đây phải dẫn đến một lỗi:

INSERT INTO my_table VALUES (1, 1, 2, NULL);
INSERT INTO my_table VALUES (2, 1, 2, NULL);

Nhưng nó không hoạt động như mong đợi vì theo tài liệu, hai NULLgiá trị không được so sánh với nhau, vì vậy cả hai lần chèn đều không có lỗi.

Làm thế nào tôi có thể đảm bảo ràng buộc duy nhất của mình ngay cả khi id_Ccó thể NULLtrong trường hợp này? Trên thực tế, câu hỏi thực sự là: tôi có thể đảm bảo loại duy nhất này trong "sql thuần" hay tôi phải thực hiện nó ở cấp độ cao hơn (java trong trường hợp của tôi)?


Vì vậy, giả sử bạn có các giá trị (1,2,1)(1,2,2)trong các (A,B,C)cột. Có nên (1,2,NULL)cho phép thêm hay không?
ypercubeᵀᴹ

A và B không thể là null nhưng C có thể là null hoặc bất kỳ giá trị nguyên dương nào. Vì vậy (1,2,3) và (2,4, null) là hợp lệ nhưng (null, 2,3) hoặc (1, null, 4) không hợp lệ. Và [(1,2, null), (1,2,3)] không phá vỡ ràng buộc duy nhất nhưng [(1,2, null), (1,2, null)] phải phá vỡ nó.
Manuel Leduc

2
Có bất kỳ giá trị nào sẽ không bao giờ xuất hiện trong các cột đó (như các giá trị âm không?)
a_horse_with_no_name

Bạn không phải gắn nhãn các ràng buộc của bạn trong pg. Nó sẽ tự động tạo ra một tên. Chỉ cần FYI.
Evan Carroll

Câu trả lời:


94

Bạn có thể làm điều đó trong SQL thuần túy . Tạo một chỉ số duy nhất một phần ngoài đến một trong những bạn có:

CREATE UNIQUE INDEX ab_c_null_idx ON my_table (id_A, id_B) WHERE id_C IS NULL;

Bằng cách này bạn có thể nhập vào (a, b, c)trong bảng của mình:

(1, 2, 1)
(1, 2, 2)
(1, 2, NULL)

Nhưng không ai trong số này lần thứ hai.

Hoặc sử dụng haiUNIQUE chỉ mục một phần và không có chỉ mục hoàn chỉnh (hoặc ràng buộc). Giải pháp tốt nhất phụ thuộc vào chi tiết yêu cầu của bạn. So sánh:

Mặc dù đây là một cách hiệu quả cho một cột nullable duy nhất trong UNIQUEchỉ mục, nhưng nó nhanh chóng vượt khỏi tầm kiểm soát. Thảo luận về điều này - và cách sử dụng UPSERT với các chỉ mục một phần:

Ngoài ra

Không sử dụng cho các định danh trường hợp hỗn hợp mà không có dấu ngoặc kép trong PostgreSQL.

Bạn có thể coi một serialcột là khóa chính hoặc một IDENTITYcột trong Postgres 10 trở lên. Liên quan:

Vì thế:

CREATE TABLE my_table (
   my_table_id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY  -- for pg 10+
-- my_table_id bigserial PRIMARY KEY  -- for pg 9.6 or older
 , id_a int8 NOT NULL
 , id_b int8 NOT NULL
 , id_c int8
 , CONSTRAINT u_constraint UNIQUE (id_a, id_b, id_c)
);

Nếu bạn không mong đợi hơn 2 tỷ hàng (> 2147483647) trong suốt vòng đời của bảng (bao gồm các hàng bị lãng phí và bị xóa), hãy xem xét integer(4 byte) thay vì bigint(8 byte).


1
Các tài liệu ủng hộ phương pháp này, Thêm một ràng buộc duy nhất sẽ tự động tạo một chỉ mục cây B duy nhất trên cột hoặc nhóm các cột được liệt kê trong ràng buộc. Một hạn chế duy nhất chỉ bao gồm một số hàng không thể được viết dưới dạng một ràng buộc duy nhất, nhưng có thể thực thi một hạn chế đó bằng cách tạo một chỉ mục một phần duy nhất.
Evan Carroll

12

Tôi có cùng một vấn đề và tôi đã tìm ra một cách khác để có NULL duy nhất vào bảng.

CREATE UNIQUE INDEX index_name ON table_name( COALESCE( foreign_key_field, -1) )

Trong trường hợp của tôi, trường foreign_key_fieldlà một số nguyên dương và sẽ không bao giờ là -1.

Vì vậy, để trả lời Leduc bằng tay, một giải pháp khác có thể là

CREATE UNIQUE INDEX  u_constrainte (COALESCE(id_a, -1), COALESCE(id_b,-1),COALESCE(id_c, -1) )

Tôi giả sử rằng id sẽ không là -1.

Lợi thế của việc tạo ra một chỉ số một phần là gì?
Trong trường hợp bạn không có mệnh đề NOT NULL, id_a, id_bid_ccó thể được NULL cùng chỉ một lần.
Với một chỉ mục một phần, 3 trường có thể là NULL nhiều lần.


3
> Lợi thế của việc tạo một chỉ mục một phần là gì? Cách bạn đã thực hiện có COALESCEthể có hiệu quả trong việc hạn chế các mục trùng lặp, nhưng chỉ mục sẽ không hữu ích trong việc truy vấn vì chỉ mục biểu thức có thể sẽ không khớp với biểu thức truy vấn. Đó là, trừ khi bạn SELECT COALESCE(col, -1) ...sẽ không đạt được chỉ số.
Bo Jeanes

@BoJeanes Chỉ mục chưa được tạo cho vấn đề hiệu suất. Nó đã được tạo ra để đáp ứng đầy đủ yêu cầu kinh doanh.
Lục M

8

Một Null có thể có nghĩa là giá trị đó không được biết cho hàng đó vào lúc này nhưng sẽ được thêm vào, khi được biết, trong tương lai (ví dụ FinishDatecho việc chạy Project) hoặc không có giá trị nào có thể được áp dụng cho hàng đó (ví dụ EscapeVelocitycho lỗ đen Star).

Theo tôi, tốt hơn hết là bình thường hóa các bảng bằng cách loại bỏ tất cả Null.

Trong trường hợp của bạn, bạn muốn cho phép NULLstrong cột của mình, nhưng bạn chỉ muốn một NULLcho phép. Tại sao? Loại mối quan hệ này là gì giữa hai bảng?

Có lẽ bạn chỉ cần thay đổi cột thành NOT NULLvà lưu trữ, thay vì NULL, một giá trị đặc biệt (như -1) được biết là không bao giờ xuất hiện. Điều này sẽ giải quyết vấn đề ràng buộc duy nhất (nhưng có thể có các tác dụng phụ không mong muốn khác. Ví dụ: sử dụng -1để có nghĩa là "không biết / không áp dụng" sẽ làm lệch bất kỳ phép tính tổng hoặc trung bình nào trên cột. Hoặc tất cả các phép tính như vậy sẽ phải thực hiện vào tài khoản giá trị đặc biệt và bỏ qua nó.)


2
Trong trường hợp của tôi, NULL thực sự là NULL (id_C là khóa ngoại đối với bảng_c cho ví dụ để nó không có giá trị -1), điều đó có nghĩa là chúng không có mối quan hệ giữa "my_table" và "table_c". Vì vậy, nó có một dấu hiệu chức năng. Nhân tiện [(1, 1,1, null), (2, 1,2, null), (3,2,4, null)] là một danh sách hợp lệ của dữ liệu được chèn.
Manuel Leduc

1
Nó không thực sự là Null như được sử dụng trong SQL vì bạn chỉ muốn một trong tất cả các hàng. Bạn có thể thay đổi lược đồ cơ sở dữ liệu của mình bằng cách thêm -1 vào bảng_c hoặc bằng cách thêm một bảng khác (sẽ là siêu kiểu cho kiểu phụ bảng_c).
ypercubeᵀᴹ

3
Tôi chỉ muốn chỉ ra cho @Manuel rằng ý kiến ​​về null trong câu trả lời này không được tổ chức phổ biến, và còn nhiều tranh cãi. Nhiều người, như tôi, nghĩ rằng null có thể được sử dụng cho bất kỳ mục đích nào bạn muốn (nhưng chỉ nên có nghĩa là một điều cho mỗi lĩnh vực và được ghi lại, có thể trong tên trường hoặc nhận xét cột)
Jack Douglas

1
Bạn không thể sử dụng giá trị giả khi cột của bạn là PHÍM NGOẠI.
Lục M

1
+1 Tôi với bạn: nếu chúng tôi muốn một số kết hợp các cột là duy nhất, thì bạn cần xem xét một thực thể trong đó tổ hợp các cột này là PK. Lược đồ cơ sở dữ liệu của OP có thể nên thay đổi thành bảng cha và bảng con.
AK
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.