PostgreSQL ข้อ จำกัด หลายคอลัมน์ที่ไม่ซ้ำกันและค่าเป็นศูนย์


94

ฉันมีตารางดังนี้:

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

และฉันต้องการ(id_A, id_B, id_C)ชัดเจนในทุกสถานการณ์ ดังนั้นส่วนแทรกสองรายการต่อไปนี้ต้องส่งผลให้เกิดข้อผิดพลาด:

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

แต่มันไม่ทำงานตามที่คาดไว้เพราะตามเอกสารประกอบสองNULLค่านั้นไม่ได้ถูกนำมาเปรียบเทียบกันดังนั้นเม็ดมีดทั้งสองผ่านโดยไม่มีข้อผิดพลาด

ฉันสามารถรับประกันข้อ จำกัด ที่ไม่ซ้ำกันของฉันแม้ว่าid_Cอาจจะเป็นNULLในกรณีนี้หรือไม่? ที่จริงแล้วคำถามจริงคือ: ฉันสามารถรับประกันความเป็นเอกลักษณ์แบบนี้ใน "pure sql" หรือฉันต้องใช้มันในระดับที่สูงขึ้น (java ในกรณีของฉัน)?


ดังนั้นสมมติว่าคุณมีค่า(1,2,1)และ(1,2,2)ใน(A,B,C)คอลัมน์ ควร(1,2,NULL)ได้รับอนุญาตให้เพิ่มหรือไม่?
ypercubeᵀᴹ

A และ B ไม่สามารถเป็นโมฆะ แต่ C สามารถเป็นโมฆะหรือค่าจำนวนเต็มบวกใด ๆ ดังนั้น (1,2,3) และ (2,4, null) จึงใช้ได้ แต่ (null, 2,3) หรือ (1, null, 4) ไม่ถูกต้อง และ [(1,2, null), (1,2,3)] ไม่ทำลายข้อ จำกัด ที่ไม่ซ้ำกัน แต่ [(1,2, null), (1,2, null)] ต้องทำลายมัน
Manuel Leduc

2
มีค่าใดบ้างที่จะไม่ปรากฏในคอลัมน์เหล่านั้น (เช่นค่าลบ)
a_horse_with_no_name

คุณไม่ต้องติดป้ายกำกับข้อ จำกัด ของคุณในหน้า pg มันจะสร้างชื่อโดยอัตโนมัติ เพียงแค่ FYI
Evan Carroll

คำตอบ:


94

คุณสามารถทำในSQL บริสุทธิ์ สร้างดัชนีเฉพาะบางส่วน เพิ่มเติมจากดัชนีที่คุณมี:

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

วิธีนี้คุณสามารถป้อน(a, b, c)ในตารางของคุณ:

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

แต่ไม่มีอีกเป็นครั้งที่สอง

หรือใช้ดัชนีบางส่วนสองรายการUNIQUEและไม่มีดัชนีสมบูรณ์ (หรือข้อ จำกัด ) ทางออกที่ดีที่สุดขึ้นอยู่กับรายละเอียดความต้องการของคุณ เปรียบเทียบ:

แม้ว่าสิ่งนี้จะสวยงามและมีประสิทธิภาพสำหรับคอลัมน์ nullable เดียวในUNIQUEดัชนี แต่มันก็กลายเป็นสิ่งที่เกินความจำเป็น อธิบายเรื่องนี้และวิธีใช้ UPSERT กับดัชนีบางส่วน:

มานี

ไม่ใช้สำหรับตัวระบุตัวพิมพ์เล็กและตัวพิมพ์ใหญ่ที่ไม่มีเครื่องหมายคำพูดคู่ใน PostgreSQL

คุณอาจพิจารณาว่าserialคอลัมน์เป็นคีย์หลักหรือIDENTITYคอลัมน์ใน Postgres 10 หรือใหม่กว่า ที่เกี่ยวข้อง:

ดังนั้น:

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

หากคุณไม่คาดหวังมากกว่า 2 พันล้านแถว (> 2147483647) ตลอดอายุการใช้งานของตารางของคุณ (รวมถึงแถวเสียและแถวที่ลบ) ให้พิจารณาinteger(4 ไบต์) แทนbigint(8 ไบต์)


1
เอกสารสนับสนุนวิธีนี้การเพิ่มข้อ จำกัด ที่ไม่ซ้ำกันจะสร้างดัชนี B-tree ที่ไม่ซ้ำกันโดยอัตโนมัติในคอลัมน์หรือกลุ่มของคอลัมน์ที่ระบุไว้ในข้อ จำกัด ข้อ จำกัด ที่ไม่ซ้ำกันซึ่งครอบคลุมเฉพาะบางแถวไม่สามารถเขียนเป็นข้อ จำกัด ที่ไม่ซ้ำกัน แต่เป็นไปได้ที่จะบังคับใช้ข้อ จำกัด ดังกล่าวโดยการสร้างดัชนีบางส่วนที่ไม่ซ้ำกัน
Evan Carroll

12

ฉันมีปัญหาเดียวกันและฉันพบวิธีอื่นในการมีค่า NULL ที่ไม่ซ้ำกันลงในตาราง

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

ในกรณีของฉันฟิลด์foreign_key_fieldเป็นจำนวนเต็มบวกและจะไม่เป็น -1

ดังนั้นในการตอบด้วยตนเอง Leduc โซลูชันอื่นอาจเป็น

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

ฉันคิดว่ารหัสจะไม่เป็น -1

ข้อดีของการสร้างดัชนีบางส่วนคืออะไร
ในกรณีที่คุณไม่ได้มีคำสั่งไม่เป็นโมฆะ, id_a, id_bและid_cสามารถเป็นโมฆะด้วยกันเพียงครั้งเดียว
ด้วยดัชนีบางส่วนฟิลด์ 3 อาจเป็น NULL มากกว่าหนึ่งครั้ง


3
> อะไรคือข้อดีของการสร้างดัชนีบางส่วน วิธีที่คุณดำเนินการด้วยCOALESCEจะมีประสิทธิภาพในการ จำกัด การซ้ำซ้อน แต่ดัชนีจะไม่เป็นประโยชน์มากในการสืบค้นเนื่องจากดัชนีนิพจน์ที่อาจไม่ตรงกับนิพจน์การค้นหา นั่นคือถ้าคุณSELECT COALESCE(col, -1) ...ไม่ได้กดปุ่มดัชนี
Bo Jeanes

@BoJeanes ดัชนีไม่ได้ถูกสร้างขึ้นสำหรับปัญหาด้านประสิทธิภาพ มันถูกสร้างขึ้นเพื่อเติมเต็มความต้องการทางธุรกิจ
Luc M

8

Null อาจหมายถึงค่านั้นไม่เป็นที่รู้จักสำหรับแถวนั้นในขณะนี้ แต่จะถูกเพิ่มเมื่อทราบในอนาคต (ตัวอย่างFinishDateสำหรับการรันProject) หรือไม่มีค่าใดที่สามารถนำไปใช้กับแถวนั้น (ตัวอย่างEscapeVelocityสำหรับหลุมดำStar)

ในความคิดของฉันมักจะดีกว่าที่จะทำให้ปกติตารางโดยการกำจัด Nulls ทั้งหมด

ในกรณีของคุณคุณต้องการให้NULLsในคอลัมน์ของคุณ แต่คุณต้องการเพียงคนเดียวNULLที่จะได้รับอนุญาต ทำไม? ความสัมพันธ์แบบไหนกันระหว่างสองตารางนี้?

บางทีคุณสามารถเปลี่ยนคอลัมน์เป็นNOT NULLและจัดเก็บได้แทนที่จะNULLเป็นค่าพิเศษ (เหมือน-1) ที่ไม่เคยปรากฏมาก่อน วิธีนี้จะช่วยแก้ปัญหาข้อ จำกัด ที่ไม่ซ้ำกัน (แต่อาจมีผลข้างเคียงอื่น ๆ ที่อาจไม่พึงประสงค์ตัวอย่างเช่นการใช้-1เพื่อหมายถึง "ไม่ทราบ / ไม่ได้ใช้" จะบิดเบือนการคำนวณผลรวมหรือค่าเฉลี่ยในคอลัมน์ใด ๆ พิจารณาค่าพิเศษและละเว้นมัน)


2
ในกรณีของฉัน NULL นั้นเป็น NULL จริงๆ (id_C เป็น foreign key to table_c for exemple ดังนั้นจึงไม่มีค่า -1) หมายความว่าไม่มีความสัมพันธ์ระหว่าง "my_table" และ "table_c" ดังนั้นมันจึงมีความหมายที่ใช้งานได้ โดยวิธี [(1, 1,1, null), (2, 1,2, null), (3,2,4, null)] เป็นรายการที่ถูกต้องของข้อมูลที่ถูกแทรก
Manuel Leduc

1
ไม่ใช่ Null จริง ๆ ที่ใช้ใน SQL เพราะคุณต้องการเพียงแถวเดียวในทุกแถว คุณสามารถเปลี่ยนสคีมาฐานข้อมูลของคุณได้โดยเพิ่ม -1 ถึง table_c หรือเพิ่มอีกตารางหนึ่ง (ซึ่งจะเป็น supertype เป็น subtype table_c)
ypercubeᵀᴹ

3
ฉันแค่อยากจะชี้ให้เห็นถึง @Manuel ว่าความคิดเห็นเกี่ยวกับโมฆะในคำตอบนี้ไม่ได้จัดขึ้นในระดับสากลและมีการถกเถียงกันมากมาย หลายคนอย่างฉันคิดว่า null สามารถใช้เพื่อวัตถุประสงค์ใด ๆ ที่คุณต้องการ (แต่ควรหมายถึงสิ่งเดียวสำหรับแต่ละฟิลด์และจัดทำเป็นเอกสารอาจเป็นในชื่อฟิลด์หรือข้อคิดเห็นคอลัมน์)
Jack Douglas

1
คุณไม่สามารถใช้ค่าดัมมี่เมื่อคอลัมน์ของคุณคือคีย์ต่างประเทศ
Luc M

1
+1 ฉันอยู่กับคุณ: หากเราต้องการให้ชุดค่าผสมของคอลัมน์ไม่เหมือนกันคุณต้องพิจารณาเอนทิตีที่มีชุดค่าผสมของคอลัมน์นี้คือ PK สคีมาฐานข้อมูลของ OPs อาจเปลี่ยนเป็นตารางหลักและรายการย่อย
AK
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.