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


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.