การใช้คีย์ต่างประเทศหลายอันคั่นด้วยเครื่องหมายจุลภาคผิดหรือไม่และถ้าใช่ทำไม


31

มีสองตาราง: และDeal DealCategoriesดีลหนึ่งสามารถมีได้หลายประเภทดีล

ดังนั้นวิธีที่เหมาะสมควรทำตารางที่DealCategoriesมีโครงสร้างดังนี้

DealCategoryId (PK)
DealId (FK)
DealCategoryId (FK)

อย่างไรก็ตามทีมงานภายนอกของเราเก็บหลายหมวดหมู่ไว้ใน Dealตารางด้วยวิธีนี้:

DealId (PK)
DealCategory -- In here they store multiple deal ids separated by commas like this: 18,25,32.

ฉันรู้สึกว่าสิ่งที่พวกเขาทำผิด แต่ฉันไม่รู้วิธีอธิบายอย่างชัดเจนว่าทำไมสิ่งนี้ไม่ถูกต้อง

ฉันจะอธิบายให้พวกเขาฟังได้อย่างไรว่าสิ่งนี้ผิด หรือบางทีฉันเป็นคนที่ผิดและเป็นที่ยอมรับ?



7
ยิงทีม outsourced ทันทีก่อนที่พวกเขาจะทำอันตรายใด ๆ เพิ่มเติม ... (-_-)
Rafa

คำตอบ:


49

ใช่มันเป็นความคิดที่แย่มาก

แทนที่จะไป:

SELECT Deal.Name, DealCategory.Name
FROM Deal
  INNER JOIN
     DealCategories ON Deal.DealID = DealCategories.DealID
  INNER JOIN
     DealCategory ON DealCategories.DealCategoryID = DealCategory.DealCategoryID
WHERE Deal.DealID = 1234

ตอนนี้คุณต้องไป:

SELECT Deal.ID, Deal.Name, DealCategories
FROM Deal
WHERE Deal.DealID = 1234

จากนั้นคุณต้องทำสิ่งต่าง ๆ ในรหัสแอปพลิเคชันของคุณเพื่อแยกรายการจุลภาคนั้นออกเป็นตัวเลขแต่ละตัวแล้วค้นหาฐานข้อมูลแยกจากกัน:

SELECT DealCategory.Name
FROM DealCategory
WHERE DealCategory.DealCategoryID IN (<<that list from before>>)

antipattern การออกแบบนี้เกิดจากความเข้าใจผิดที่สมบูรณ์ของการสร้างแบบจำลองเชิงสัมพันธ์ (คุณไม่ต้องกลัวตาราง Tables เป็นเพื่อนของคุณใช้พวกมัน) หรือความเชื่อที่เข้าใจผิดอย่างผิดปกติมันเร็วกว่าที่จะแยกรายการด้วยเครื่องหมายจุลภาค ในรหัสแอปพลิเคชันมากกว่าที่จะเพิ่มตารางลิงค์ ( ไม่เคยเป็น) ตัวเลือกที่สามคือพวกเขาไม่มั่นใจ / มีความสามารถเพียงพอกับ SQL ในการตั้งค่าคีย์ต่างประเทศ แต่ถ้าเป็นเช่นนั้นพวกเขาไม่ควรทำอะไรกับการออกแบบโมเดลเชิงสัมพันธ์

SQL Antipatterns (Karwin, 2010) อุทิศทั้งบทให้กับ antipattern นี้ (ซึ่งเขาเรียกว่า 'Jaywalking') หน้า 15-23 นอกจากนี้ผู้เขียนได้โพสต์ในคำถามที่คล้ายกันมากกว่าที่ SO ประเด็นสำคัญที่เขาบันทึกไว้ (ตามที่ใช้กับตัวอย่างนี้) คือ:

  • การสืบค้นข้อตกลงทั้งหมดในหมวดหมู่ที่เฉพาะเจาะจงนั้นค่อนข้างซับซ้อน (วิธีที่ง่ายที่สุดในการแก้ปัญหานั้นคือนิพจน์ทั่วไป แต่นิพจน์ทั่วไปเป็นปัญหาในตัวของมันเอง)
  • คุณไม่สามารถบังคับใช้ Referential Integrity ได้หากไม่มีความสัมพันธ์กับ Foreign Key หากคุณลบ DealCategory nr # 26 ในรหัสแอปพลิเคชันของคุณคุณต้องทำตามข้อตกลงแต่ละข้อเพื่อค้นหาการอ้างอิงถึงหมวดหมู่ # 26 และลบออก นี่คือสิ่งที่ควรจัดการที่ชั้นข้อมูลและการจัดการกับมันในแอปพลิเคชันของคุณเป็นสิ่งที่เลวร้ายมากเป็นสิ่งที่เลวร้ายมาก
  • คำสั่งรวม ( COUNT, SUMฯลฯ ) อีกครั้งแตกต่างจาก 'ซับซ้อน' ถึง 'ไปไม่ได้เกือบ' ถามนักพัฒนาของคุณว่าพวกเขาจะทำให้คุณได้รับรายชื่อหมวดหมู่ทั้งหมดที่มีจำนวนข้อเสนอในหมวดหมู่นั้น ๆ อย่างไร ด้วยการออกแบบที่เหมาะสมนั่นคือ SQL สี่บรรทัด
  • การอัปเดตกลายเป็นเรื่องยากมากขึ้น (เช่นคุณมีดีลอยู่ในห้าหมวดหมู่ แต่คุณต้องการลบสองรายการและเพิ่มอีกสามหมวดหมู่) นั่นคือ SQL สามบรรทัดที่มีการออกแบบที่เหมาะสม
  • ในที่สุดคุณจะพบVARCHARข้อจำกัดความยาวของรายการ แม้ว่าคุณจะมีรายการที่คั่นด้วยเครื่องหมายจุลภาคที่มีมากกว่า 4,000 ตัวอักษร แต่โอกาสในการแยกมอนสเตอร์นั้นก็จะช้าเหมือนนรกอยู่ดี
  • การดึงรายการออกจากฐานข้อมูลแยกออกแล้วกลับไปที่ฐานข้อมูลสำหรับแบบสอบถามอื่นช้ากว่าภายในหนึ่งแบบสอบถาม

TLDR: มันเป็นข้อบกพร่องพื้นฐานการออกแบบมันไม่ได้ปรับขนาดได้ดีมันนำเสนอความซับซ้อนเพิ่มเติมแม้กระทั่งการสืบค้นที่ง่ายที่สุด


1
ไซม่อนบางคนทำคำถามเดียวกัน ( dba.stackexchange.com/questions/17824/ … ) แต่ฉันยังไม่ชัดเจนว่าทำไม FK และ PK เดียวกันจึงอยู่ในตารางเดียวกันนั่นทำให้เบรค 3FN
jcho360

2
ฉันไม่แน่ใจว่าพวกเขาต้องการมีความสัมพันธ์แบบหลายต่อหลายอย่างระหว่างดีลและหมวดหมู่หรือทายาทของหมวดหมู่บางประเภท ไม่ว่าจะด้วยวิธีใดก็ตามมันเป็นสิ่งที่ไม่ควรมองข้ามสำหรับประเด็นหลักว่าการเป็นเขตข้อมูลที่คั่นด้วยจุลภาคแทนที่จะเป็นตารางลิงก์เป็นแนวคิดที่ไม่ดี
Simon Righarts

4

อย่างไรก็ตามทีมงานภายนอกของเราเก็บหลายหมวดหมู่ไว้ในตารางดีลด้วยวิธีนี้:

DealId (PK) DealCategory - ในที่นี่พวกเขาเก็บรหัสการจัดการหลายรายการคั่นด้วยเครื่องหมายจุลภาคเช่นนี้: 18,25,32

ที่จริงการออกแบบที่ดีถ้าคุณเพียงต้องแบบสอบถามสำหรับประเภทการจัดการที่ได้รับ

แต่มันแย่มากถ้าคุณต้องการทราบข้อเสนอทั้งหมดในหมวดหมู่ที่กำหนด

และยังทำให้ยากและมีข้อผิดพลาดเกิดขึ้นได้ง่ายเช่นการอัปเดตการนับการเข้าร่วม ฯลฯ

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

เช่นเดียวกับการเพิ่มประสิทธิภาพรูปแบบอื่น ๆ คุณจำเป็นต้องรู้ว่าแบบสอบถามใดที่คุณจะเรียกใช้ก่อนที่คุณจะสามารถตัดสินใจได้ว่า


1
คุณคิดว่าสตริงที่มีรหัสลูกที่คั่นด้วยเครื่องหมายจุลภาคนั้นมีประโยชน์หรือไม่ select * from DealCategories where DealId in (1,2,3,4,...)ผมหมายถึงแอพลิเคชันที่มีการอ่านครั้งแรกแล้วแยกรหัสและสอบถามเด็กทุกคนชอบ คุณมีประสบการณ์เกี่ยวกับการออกแบบฐานข้อมูลมากกว่าฉันดังนั้นบางทีคุณอาจมีเหตุผลที่ดีในบางกรณีสำหรับ "การปรับจูนมาก" ในกรณีที่เฉพาะเจาะจงมาก ความคิดเดียวของฉันที่จะแสดงให้เห็นว่านี่เป็นselectภาระที่สูงมากสำหรับ Deal / DealCategory สิ่งนี้ดูเหมือนกับทีมงาน outsource บางคนที่ไม่มีความรู้ด้านการออกแบบฐานข้อมูลเลยนอกเหนือจากการสร้างตาราง
Erik Hart

1
@ErikHart นี่คือการทำให้เป็นปกติและมันจะมีประโยชน์ แต่ประเด็นของฉันคือมันขึ้นอยู่กับการสืบค้นที่คุณต้องใช้ทั้งหมด คุณถูกต้องที่การทำให้เป็นปกตินั้นทำให้แบบสอบถามทั้งหมดทำงานได้แย่ลงยกเว้นแบบสอบถามเดียวที่ปรับให้เหมาะสม หากคุณจำเป็นต้องเรียกใช้แบบสอบถามหนึ่งและคุณไม่สนใจเกี่ยวกับแบบสอบถามอื่น ๆ ก็ชนะ แต่นี่เป็นกรณีที่หายากเพราะโดยทั่วไปแล้วเราต้องการความยืดหยุ่นในการสืบค้นข้อมูลในหลากหลายวิธี
Bill Karwin

1
@ErikHart หากทีมงาน outsource ได้รับข้อกำหนดโครงการที่มีเพียงหนึ่งแบบสอบถามกับข้อมูลนี้พวกเขาสามารถออกแบบการปรับให้เหมาะสมสำหรับแบบสอบถามเฉพาะนั้นเท่านั้น พูดอีกอย่างว่า "คุณถามแล้วคุณเข้าใจแล้ว" แต่ผู้ให้บริการเอาท์ซอร์สไม่มีเหตุผลที่จะวางแผนสำหรับการใช้ข้อมูลในอนาคต - พวกเขาใช้แอปพลิเคชันกับจดหมายของสิ่งที่เขียนไว้ในข้อมูลจำเพาะ
Bill Karwin

1

ค่าหลายค่าในคอลัมน์ขัดกับรูปแบบปกติที่ 1

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

การใช้งานที่ถูกต้องจะเป็นตารางทางแยกเช่น "DealDealCategories" พร้อมกับ DealId และ DealCategoryId

การใช้งานลำดับชั้นไม่ดี?

นอกจากนี้ FK ใน DealCategories ไปยัง DealCategory อื่นดูเหมือนว่าการใช้งานที่ไม่ดีของลำดับชั้น / ทรีของ DealCategories การทำงานกับต้นไม้ผ่านความสัมพันธ์ของผู้ปกครอง (เรียกว่ารายการ adjacency) เป็นความเจ็บปวด!

ตรวจสอบชุดซ้อน (ดีในการอ่าน แต่ยากที่จะแก้ไข) และตารางการปิด (ประสิทธิภาพโดยรวมที่ดีที่สุด แต่อาจใช้หน่วยความจำสูง - อาจไม่มากสำหรับ DealCategories ของคุณ) เมื่อใช้งานลำดับชั้น!

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