เกิดอะไรขึ้นกับคอลัมน์ที่ไม่มีค่าในคีย์หลักคอมโพสิต


149

ORACLE ไม่อนุญาตให้มีค่า NULL ในคอลัมน์ใด ๆ ที่ประกอบด้วยคีย์หลัก ดูเหมือนว่าจะเป็นจริงสำหรับระบบ "ระดับองค์กร" อื่น ๆ ส่วนใหญ่

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

เหตุใดข้อ จำกัด ที่ไม่ซ้ำกันจึงสามารถมีค่า NULL ได้ แต่คีย์หลักไม่สามารถทำได้ มีเหตุผลเชิงตรรกะสำหรับสิ่งนี้หรือมีข้อ จำกัด ทางเทคนิคมากกว่านี้หรือไม่?


คำตอบ:


216

คีย์หลักใช้สำหรับระบุแถวที่ไม่ซ้ำกัน สิ่งนี้ทำได้โดยการเปรียบเทียบทุกส่วนของคีย์กับอินพุต

ตามนิยาม NULL ไม่สามารถเป็นส่วนหนึ่งของการเปรียบเทียบที่ประสบความสำเร็จ แม้การเปรียบเทียบกับตัวเอง ( NULL = NULL) จะล้มเหลว นี่หมายความว่าคีย์ที่มี NULL จะไม่ทำงาน

นอกจากนี้ NULL ได้รับอนุญาตใน foreign key เพื่อทำเครื่องหมายความสัมพันธ์เสริม (*) การอนุญาตใน PK เช่นกันจะทำให้เกิดข้อผิดพลาดนี้


(*)คำเตือน: การมีคีย์ต่างประเทศแบบ nullable ไม่ได้เป็นการออกแบบฐานข้อมูลเชิงสัมพันธ์ที่สะอาด

หากมีสองหน่วยงานAและBที่Aสามารถเลือกที่จะเกี่ยวข้องกับBการแก้ปัญหาการทำความสะอาดคือการสร้างตารางความละเอียด (ขอบอกAB) ตารางที่จะเชื่อมโยงAกับB: ถ้ามีเป็นความสัมพันธ์ที่แล้วก็จะมีการบันทึกถ้ามีไม่ได้แล้วมันจะไม่


5
ฉันเปลี่ยนคำตอบที่ยอมรับเป็นคำตอบนี้แล้ว ตัดสินโดยโหวตคำตอบนี้เป็นที่ชัดเจนกับผู้คนมากขึ้น ฉันยังรู้สึกว่าคำตอบของ Tony Andrews อธิบายถึงความตั้งใจในการออกแบบนี้ดีกว่า ตรวจสอบออกเช่นกัน!
Roman Starkov

2
ถาม: เมื่อใดที่คุณต้องการ NULL FK แทนที่จะเป็นแถว? ตอบ: เฉพาะในเวอร์ชันของสคีมาที่ทำให้เป็นสภาวะปกติเพื่อการปรับให้เหมาะสม ในสคีมาที่ไม่ธรรมดาปัญหาที่ไม่ธรรมดาเช่นนี้อาจทำให้เกิดปัญหาเมื่อต้องการคุณลักษณะใหม่ โอตอฮาฝูงชนที่ออกแบบเว็บไซต์ไม่สนใจ อย่างน้อยฉันจะเพิ่มข้อควรระวังเกี่ยวกับสิ่งนี้แทนที่จะทำให้มันฟังดูเหมือนแนวคิดการออกแบบที่ดี
zxq9

3
"การมีคีย์ต่างประเทศแบบ nullable ไม่ได้เป็นการออกแบบฐานข้อมูลเชิงสัมพันธ์ที่ไม่สะอาด" - การออกแบบฐานข้อมูลที่ไม่มีค่าว่าง (รูปแบบปกติที่หก) เพิ่มความซับซ้อนอย่างคงเส้นคงวาการประหยัดพื้นที่ที่ได้รับมักจะมีค่ามากกว่าโดยการทำงานของโปรแกรมเมอร์เพิ่มเติมที่จำเป็นเพื่อให้ได้รับผลประโยชน์เหล่านั้น
Dai

1
เกิดอะไรขึ้นถ้ามันเป็นตารางความละเอียด ABC? ด้วยตัวเลือก C
Bart Calixto

1
ฉันพยายามหลีกเลี่ยงการเขียน "เพราะมาตรฐานห้ามมัน" เพราะนี่ไม่ได้อธิบายอะไรเลย
Tomalak

62

คีย์หลักกำหนดตัวระบุที่ไม่ซ้ำกันสำหรับทุกแถวในตาราง: เมื่อตารางมีคีย์หลักคุณจะมีวิธีรับประกันให้เลือกแถวใดก็ได้จากมัน

ข้อ จำกัด เฉพาะไม่จำเป็นต้องระบุทุกแถว มันแค่ระบุว่าถ้าแถวมีค่าในคอลัมน์ของพวกเขาแล้วพวกเขาจะต้องไม่ซ้ำกัน นี่ไม่เพียงพอที่จะระบุทุก ๆแถวซึ่งเป็นสิ่งที่คีย์หลักต้องทำ


10
ใน Sql Server ข้อ จำกัด ที่ไม่ซ้ำใครที่มีคอลัมน์ nullable ช่วยให้ค่า 'null' ในคอลัมน์นั้นเพียงครั้งเดียว (กำหนดค่าที่เหมือนกันสำหรับคอลัมน์อื่น ๆ ของข้อ จำกัด ) ดังนั้นข้อ จำกัด ที่เป็นเอกลักษณ์ดังกล่าวจึงทำงานเหมือน pk ที่มีคอลัมน์ที่ไม่มีค่าได้
เจอราร์ด

ฉันยืนยันเช่นเดียวกันสำหรับ Oracle (11.2)
Alexander Malakhov

2
ใน Oracle (ฉันไม่รู้ SQL Server) ตารางสามารถมีหลายแถวที่คอลัมน์ทั้งหมดในข้อ จำกัด ที่ไม่ซ้ำกันเป็นโมฆะ อย่างไรก็ตามหากบางคอลัมน์ในข้อ จำกัด ที่ไม่ซ้ำกันนั้นไม่เป็นโมฆะและบางคอลัมน์เป็นโมฆะจะมีการบังคับใช้ความเป็นเอกลักษณ์
Tony Andrews

สิ่งนี้นำไปใช้กับคอมโพสิต UNIQUE ได้อย่างไร
Dims

1
@Dims เช่นเดียวกับเกือบทุกอย่างในฐานข้อมูล SQL "ขึ้นอยู่กับการใช้งาน" ส่วนใหญ่แล้วคีย์หลักเป็นข้อ จำกัด ที่ไม่เหมือนใครภายใต้ แนวคิดของ "คีย์หลัก" นั้นไม่ได้พิเศษหรือมีประสิทธิภาพมากไปกว่าแนวคิดของ UNIQUE ความแตกต่างที่แท้จริงคือถ้าคุณมีสองด้านที่เป็นอิสระของตารางที่สามารถรับประกัน UNIQUE ได้คุณจะไม่มีฐานข้อมูลปกติตามคำนิยาม (คุณกำลังจัดเก็บข้อมูลสองประเภทไว้ในตารางเดียวกัน)
zxq9

46

พื้นฐานการพูดไม่มีอะไรผิดกับ NULL ในคีย์หลักแบบหลายคอลัมน์ แต่การมีสิ่งใดสิ่งหนึ่งมีความหมายว่าผู้ออกแบบไม่ได้ตั้งใจซึ่งเป็นเหตุผลว่าทำไมระบบจำนวนมากจึงเกิดข้อผิดพลาดเมื่อคุณลองทำสิ่งนี้

พิจารณากรณีของโมดูล / แพ็กเกจเวอร์ชันที่จัดเก็บเป็นชุดของฟิลด์:

CREATE TABLE module
  (name        varchar(20) PRIMARY KEY,
   description text DEFAULT '' NOT NULL);

CREATE TABLE version
  (module      varchar(20) REFERENCES module,
   major       integer NOT NULL,
   minor       integer DEFAULT 0 NOT NULL,
   patch       integer DEFAULT 0 NOT NULL,
   release     integer DEFAULT 1 NOT NULL,
   ext         varchar(20),
   notes       text DEFAULT '' NOT NULL,
   PRIMARY KEY (module, major, minor, patch, release, ext));

องค์ประกอบ 5 ประการแรกของคีย์หลักจะถูกกำหนดเป็นส่วนต่าง ๆ ของรุ่นวางจำหน่ายเป็นประจำ แต่แพคเกจบางตัวมีส่วนขยายที่กำหนดเองซึ่งโดยปกติจะไม่ใช่จำนวนเต็ม (เช่น "rc-foo" หรือ "vanilla" หรือ "เบต้า" หรืออะไรก็ตาม คนที่สี่สาขาไม่เพียงพออาจฝันถึง) หากแพ็กเกจไม่มีส่วนขยายแสดงว่าเป็น NULL ในโมเดลด้านบนและไม่มีอันตรายใด ๆ ที่จะเกิดขึ้นจากการปล่อยสิ่งต่าง ๆ

แต่สิ่งที่เป็นโมฆะ? มันควรจะแสดงถึงการขาดข้อมูลที่ไม่รู้จัก ที่กล่าวว่าอาจทำให้รู้สึกมากขึ้น:

CREATE TABLE version
  (module      varchar(20) REFERENCES module,
   major       integer NOT NULL,
   minor       integer DEFAULT 0 NOT NULL,
   patch       integer DEFAULT 0 NOT NULL,
   release     integer DEFAULT 1 NOT NULL,
   ext         varchar(20) DEFAULT '' NOT NULL,
   notes       text DEFAULT '' NOT NULL,
   PRIMARY KEY (module, major, minor, patch, release, ext));

ในรุ่นนี้ส่วน "ext" ของ tuple ไม่ใช่ NULL แต่มีค่าเริ่มต้นเป็นสตริงว่าง - ซึ่งเป็น semantically (และในทางปฏิบัติ) แตกต่างจาก NULL NULL ไม่เป็นที่รู้จักในขณะที่สตริงว่างเป็นบันทึกโดยเจตนาของ "สิ่งที่ไม่มีอยู่" กล่าวอีกนัยหนึ่ง "ว่าง" และ "ว่าง" เป็นสิ่งที่แตกต่างกัน มันคือความแตกต่างระหว่าง "ฉันไม่มีคุณค่าที่นี่" และ "ฉันไม่รู้ว่าค่านี่คืออะไร"

เมื่อคุณลงทะเบียนแพ็กเกจที่ไม่มีส่วนขยายเวอร์ชันคุณรู้ว่ามันไม่มีส่วนขยายดังนั้นสตริงที่ว่างเปล่าจึงเป็นค่าที่ถูกต้อง ค่า NULL นั้นจะถูกต้องก็ต่อเมื่อคุณไม่รู้ว่ามันมีส่วนขยายหรือไม่หรือคุณรู้ว่ามันทำ แต่ไม่รู้ว่ามันเป็นอะไร สถานการณ์นี้ง่ายขึ้นที่จะจัดการกับระบบที่ค่าสตริงเป็นบรรทัดฐานเพราะไม่มีวิธีที่จะเป็นตัวแทนของ "จำนวนเต็มที่ว่างเปล่า" นอกเหนือจากการแทรก 0 หรือ 1 ซึ่งจะจบลงด้วยการเปรียบเทียบในภายหลัง (ซึ่งมี ผลกระทบของมันเอง) *

อนึ่งทั้งสองวิธีมีผลบังคับใช้ใน Postgres (เนื่องจากเรากำลังพูดถึง "enterprise" RDMBSs) แต่ผลลัพธ์การเปรียบเทียบอาจแตกต่างกันเล็กน้อยเมื่อคุณโยน NULL ลงในส่วนผสม - เนื่องจาก NULL == "ไม่รู้" ดังนั้นทั้งหมด ผลลัพธ์ของการเปรียบเทียบที่เกี่ยวข้องกับการยกเลิกค่า NULL เนื่องจากคุณไม่สามารถรู้สิ่งที่ไม่รู้จัก อันตราย! คิดอย่างรอบคอบเกี่ยวกับสิ่งนี้หมายความว่าผลการเปรียบเทียบค่า NULL เผยแพร่ผ่านชุดการเปรียบเทียบ นี่อาจเป็นแหล่งของข้อบกพร่องที่ละเอียดอ่อนเมื่อทำการเรียงลำดับการเปรียบเทียบและอื่น ๆ

Postgres ถือว่าคุณเป็นผู้ใหญ่และสามารถตัดสินใจได้ด้วยตัวเอง Oracle และ DB2 สมมติว่าคุณไม่ได้ตระหนักว่าคุณกำลังทำอะไรที่โง่และผิดพลาด นี่คือมักจะสิ่งที่ถูกต้อง แต่ไม่เสมอ - คุณอาจจะจริงไม่ทราบและมีความเป็นโมฆะในบางกรณีและดังนั้นจึงออกจากแถวที่มีองค์ประกอบที่ไม่รู้จักกับที่รถที่มีความหมายเป็นไปไม่ได้เป็นพฤติกรรมที่ถูกต้อง

ไม่ว่าในกรณีใดคุณควรพยายามกำจัดจำนวนของเขตข้อมูล NULL ที่คุณอนุญาตให้ใช้กับทั้งสคีมาและเพิ่มขึ้นเป็นสองเท่าดังนั้นเมื่อมาถึงเขตข้อมูลที่เป็นส่วนหนึ่งของคีย์หลัก ในกรณีส่วนใหญ่การปรากฏตัวของคอลัมน์ NULL เป็นตัวบ่งชี้ของการออกแบบ schema ที่ไม่เป็นมาตรฐาน (ซึ่งตรงกันข้ามกับการออกแบบ schema โดยเจตนา) และควรคิดอย่างหนักก่อนที่จะได้รับการยอมรับ

[* หมายเหตุ: มันเป็นไปได้ที่จะสร้างประเภทที่กำหนดเองนั่นคือการรวมกันของจำนวนเต็มและประเภท "ด้านล่าง" ที่จะหมายถึง "ว่างเปล่า" ซึ่งหมายถึง "ว่าง" ซึ่งตรงข้ามกับ "ไม่รู้จัก" น่าเสียดายที่นี่มีความซับซ้อนเล็กน้อยในการดำเนินการเปรียบเทียบและโดยปกติแล้วการพิมพ์ให้ถูกต้องนั้นไม่คุ้มกับความพยายามในการฝึกฝนเพราะคุณไม่ควรได้รับอนุญาตให้ใช้NULLค่าจำนวนมากตั้งแต่แรก ที่กล่าวว่ามันจะยอดเยี่ยมถ้า RDBMSs จะรวมBOTTOMประเภทเริ่มต้นนอกเหนือจากNULLเพื่อป้องกันไม่ให้นิสัยของ conflating ความหมายของ "ไม่มีค่า" กับ "ค่าที่ไม่รู้จัก" โดยไม่ตั้งใจ ]


5
นี่คือคำตอบที่ดีมากและอธิบายมากเกี่ยวกับค่า NULL และมันมีความหมายในหลาย ๆ สถานการณ์ คุณครับตอนนี้ฉันเคารพ! แม้แต่ในวิทยาลัยฉันก็ได้รับคำอธิบายที่ดีเกี่ยวกับค่า NULL ในฐานข้อมูล ขอบคุณ!

ฉันสนับสนุนแนวคิดหลักของคำตอบนี้ แต่การเขียนเช่น 'ควรจะแสดงถึงการขาดข้อมูลไม่ทราบ', 'semantically (และในทางปฏิบัติ) แตกต่างจาก NULL', 'A NULL ไม่เป็นที่รู้จัก', 'สตริงว่างเปล่าเป็นบันทึกโดยเจตนาของ "',' NULL ==" ไม่ทราบ "", ฯลฯ คลุมเครือ & ทำให้เข้าใจผิด & คำย่อเฉพาะสำหรับคำสั่งที่ไม่อยู่จะแสดงว่าค่า NULL หรือค่าใด ๆ เป็นหรือสามารถหรือตั้งใจจะใช้ต่อโพสต์ที่เหลือ . (รวมถึงการสร้างแรงบันดาลใจในการออกแบบ (ไม่ดี) ของคุณสมบัติ SQL NULL) พวกเขาไม่ปรับหรืออธิบายอะไร ควรอธิบาย & debunked
philipxy

21

NULL == NULL -> false (อย่างน้อยในหน่วย DBMS)

ดังนั้นคุณจะไม่สามารถเรียกคืนความสัมพันธ์ใด ๆ โดยใช้ค่า NULL ได้แม้จะมีคอลัมน์เพิ่มเติมที่มีค่าจริง


1
ดูเหมือนว่าจะเป็นคำตอบที่ดีที่สุด แต่ฉันก็ยังไม่เข้าใจว่าทำไมจึงเป็นสิ่งต้องห้ามในการสร้างคีย์หลัก หากนี่เป็นเพียงปัญหาการดึงข้อมูลคุณสามารถใช้where pk_1 = 'a' and pk_2 = 'b'กับค่าปกติและเปลี่ยนเป็นwhere pk_1 is null and pk_2 = 'b'เมื่อมีค่าว่าง
EoghanM

หรือน่าเชื่อถือยิ่งขึ้นwhere (a.pk1 = b.pk1 or (a.pk1 is null and b.pk1 is null)) and (a.pk2 = b.pk2 or (a.pk2 is null and b.pk2 is null))/
Jordan Rieger

8
คำตอบที่ไม่ถูกต้อง. NULL == NULL -> UNKNOWN ไม่ผิด การดักจับคือข้อ จำกัด จะไม่ถูกพิจารณาว่าละเมิดหากผลลัพธ์ของการทดสอบไม่เป็นที่รู้จัก สิ่งนี้มักทำให้ดูเหมือนว่าการเปรียบเทียบให้ผลเป็นเท็จ แต่จริงๆแล้วไม่ได้
เออร์วิน Smout

4

คำตอบของ Tony Andrews เป็นคำตอบที่เหมาะสม แต่คำตอบที่แท้จริงคือสิ่งนี้เป็นการประชุมที่ใช้โดยชุมชนฐานข้อมูลเชิงสัมพันธ์และไม่จำเป็น อาจเป็นแบบแผนที่ดีอาจจะไม่ใช่

การเปรียบเทียบสิ่งใด ๆ กับผลลัพธ์ NULL ใน UNKNOWN (ค่าความจริงที่ 3) ดังนั้นตามที่ได้รับการแนะนำด้วย nulls ภูมิปัญญาดั้งเดิมทั้งหมดที่เกี่ยวข้องกับความเท่าเทียมกันออกไปนอกหน้าต่าง นั่นเป็นลักษณะที่เห็นได้อย่างรวดเร็วในตอนแรก

แต่ฉันไม่คิดว่ามันเป็นสิ่งจำเป็นและแม้แต่ฐานข้อมูล SQL ก็ไม่คิดว่า NULL จะทำลายความเป็นไปได้ทั้งหมดสำหรับการเปรียบเทียบ

เรียกใช้ในฐานข้อมูลของคุณแบบสอบถาม SELECT * FROM VALUES (NULL) UNION SELECT * FROM VALUES (NULL)

สิ่งที่คุณเห็นเป็นเพียงหนึ่ง tuple กับหนึ่งคุณลักษณะที่มีค่าเป็นศูนย์ ดังนั้นสหภาพจึงรับรู้ค่า NULL สองค่าเท่ากัน

เมื่อเปรียบเทียบคีย์ผสมที่มี 3 ส่วนประกอบกับ tuple ที่มี 3 คุณลักษณะ (1, 3, NULL) = (1, 3, NULL) <=> 1 = 1 และ 1 = 3 และ 3 = 3 และ NULL = NULL ผลลัพธ์ของสิ่งนี้คือ UNKNOWN .

แต่เราสามารถกำหนดตัวดำเนินการเปรียบเทียบชนิดใหม่เช่น == X == Y <=> X = Y หรือ (X Is NULL และ Y Is NULL)

การมีตัวดำเนินการความเสมอภาคแบบนี้จะทำให้คีย์ผสมกับส่วนประกอบที่เป็นโมฆะหรือคีย์ที่ไม่ใช่แบบคอมโพสิตที่ไม่มีค่าเป็นศูนย์


1
ไม่ยูเนี่ยนได้ยอมรับว่า NULL สองตัวนั้นไม่มีความแตกต่าง ซึ่งไม่เหมือนกับ "เท่ากับ" ลองใช้ UNION ALL แทนแล้วคุณจะได้สองแถว และสำหรับ "ตัวดำเนินการเปรียบเทียบชนิดใหม่" SQL มีอยู่แล้ว ไม่ได้แยกจาก แต่นั่นไม่เพียงพอ การใช้สิ่งนี้ในการสร้าง SQL เช่น NATURAL JOIN หรือส่วนคำสั่งอ้างอิงของ foreign key จะต้องการตัวเลือกเพิ่มเติมในการสร้างเหล่านั้น
เออร์วิน Smout

อ้าเออร์วิน Smout ความสุขที่ได้พบคุณในฟอรั่มนี้อย่างแท้จริง! ฉันไม่ได้ตระหนักถึง SQL ของ "IS NOT DISTINCT FROM" น่าสนใจมาก! แต่ดูเหมือนว่ามันเป็นสิ่งที่ฉันหมายถึงกับผู้ประกอบการ == แต่งหน้าของฉัน คุณช่วยอธิบายฉันได้ไหมว่าทำไมคุณถึงพูดว่า: "โดยตัวของมันเองไม่เพียงพอ"?
Rami Ojares

ข้ออ้างอิงอ้างอิงสร้างบนความเท่าเทียมกันตามคำนิยาม ชนิดของการอ้างอิงที่ตรงกับ tuple / row ลูกกับ parent tuple / row โดยยึดตามค่าแอตทริบิวต์ที่เกี่ยวข้องที่ไม่ใช่ DISTINCT แทนที่จะเป็น (the stricter) EQUAL จะต้องใช้ความสามารถในการระบุตัวเลือกนี้ แต่ไวยากรณ์ไม่ได้ อนุญาตให้มัน เหมือนกันเพื่อเข้าร่วมธรรมชาติ
เออร์วิน Smout

เพื่อให้กุญแจต่างประเทศในการทำงานการอ้างอิงจะต้องไม่ซ้ำกัน (เช่น. ค่าทั้งหมดจะต้องแตกต่างกัน) ซึ่งหมายความว่ามันอาจมีค่า Null เดียว ค่า Null ทั้งหมดสามารถอ้างถึง Null เดียวนั้นได้หากการอ้างอิงจะถูกกำหนดด้วยตัวดำเนินการ NOT DISTINCT ฉันคิดว่ามันจะดีกว่า (ในแง่ของประโยชน์มากกว่า) ด้วยการเข้าร่วม (ทั้งด้านนอกและด้านใน) ฉันคิดว่าจำนวนที่เข้มงวดจะดีกว่าเพราะ "การแข่งขันที่เป็นโมฆะ" จะทวีคูณเมื่อโมฆะทางด้านซ้ายจะจับคู่โมฆะทางด้านขวาทั้งหมด
Rami Ojares

1

ฉันยังเชื่อว่านี่เป็นข้อบกพร่องพื้นฐาน / หน้าที่ซึ่งเกิดจากการใช้เทคนิค หากคุณมีฟิลด์ตัวเลือกที่คุณสามารถระบุลูกค้าที่คุณต้องแฮ็กค่าดัมมี่มันเพราะ NULL! = NULL ไม่สวยงามโดยเฉพาะอย่างยิ่งมันเป็น "มาตรฐานอุตสาหกรรม"

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.