วิธีการออกแบบตารางผลิตภัณฑ์สำหรับผลิตภัณฑ์หลายชนิดที่แต่ละผลิตภัณฑ์มีพารามิเตอร์มากมาย


140

ฉันไม่มีประสบการณ์ในการออกแบบโต๊ะมากนัก เป้าหมายของฉันคือการสร้างตารางผลิตภัณฑ์อย่างน้อยหนึ่งรายการที่ตรงตามข้อกำหนดด้านล่าง:

  • สนับสนุนผลิตภัณฑ์หลายชนิด (TV, Phone, PC, ... ) ผลิตภัณฑ์แต่ละชนิดมีชุดพารามิเตอร์ที่แตกต่างกันเช่น:

    • โทรศัพท์จะมีสี, ขนาด, น้ำหนัก, OS ...

    • พีซีจะมี CPU, HDD, RAM ...

  • ชุดของพารามิเตอร์ต้องเป็นแบบไดนามิก คุณสามารถเพิ่มหรือแก้ไขพารามิเตอร์ใด ๆ ที่คุณต้องการ

ฉันจะปฏิบัติตามข้อกำหนดเหล่านี้ได้อย่างไรโดยไม่มีตารางแยกสำหรับผลิตภัณฑ์แต่ละประเภท

คำตอบ:


233

คุณมีอย่างน้อยห้าตัวเลือกเหล่านี้สำหรับการสร้างแบบจำลองลำดับชั้นชนิดที่คุณอธิบาย:

  • Single Table Inheritance : หนึ่งตารางสำหรับทุกประเภทผลิตภัณฑ์ที่มีคอลัมน์เพียงพอที่จะเก็บแอตทริบิวต์ทั้งหมดของทุกประเภท นี่หมายถึงคอลัมน์จำนวนมากซึ่งส่วนใหญ่เป็น NULL ในแถวที่กำหนด

  • การสืบทอดคลาสของตาราง : หนึ่งตารางสำหรับผลิตภัณฑ์ที่จัดเก็บแอตทริบิวต์ที่ใช้ร่วมกันกับผลิตภัณฑ์ทุกประเภท จากนั้นหนึ่งตารางต่อประเภทผลิตภัณฑ์จัดเก็บแอตทริบิวต์เฉพาะผลิตภัณฑ์ประเภทนั้น

  • การสืบทอดตารางคอนกรีต : ไม่มีตารางสำหรับแอตทริบิวต์ของผลิตภัณฑ์ทั่วไป แทนหนึ่งตารางต่อประเภทผลิตภัณฑ์จัดเก็บทั้งคุณสมบัติของผลิตภัณฑ์ทั่วไปและคุณลักษณะเฉพาะของผลิตภัณฑ์

  • LOB แบบอนุกรม : หนึ่งตารางสำหรับผลิตภัณฑ์ที่จัดเก็บแอตทริบิวต์ที่ใช้ร่วมกันได้กับผลิตภัณฑ์ทุกประเภท คอลัมน์พิเศษหนึ่งคอลัมน์จะเก็บ BLOB ของข้อมูลกึ่งโครงสร้างในรูปแบบ XML, YAML, JSON หรือรูปแบบอื่น ๆ BLOB นี้ช่วยให้คุณสามารถจัดเก็บแอตทริบิวต์เฉพาะผลิตภัณฑ์แต่ละประเภท คุณสามารถใช้รูปแบบการออกแบบแฟนซีเพื่ออธิบายสิ่งนี้เช่น Facade และ Memento แต่ไม่ว่าคุณจะมีกลุ่มของคุณลักษณะที่ไม่สามารถสืบค้นได้ง่ายภายใน SQL; คุณต้องดึงทั้งหยดกลับไปที่แอปพลิเคชันและเรียงลำดับที่นั่น

  • เอนทิตี - แอตทริบิวต์ - ค่า : หนึ่งตารางสำหรับผลิตภัณฑ์และหนึ่งตารางที่ pivots คุณลักษณะให้กับแถวแทนที่จะเป็นคอลัมน์ EAV ไม่ใช่การออกแบบที่ถูกต้องเกี่ยวกับกระบวนทัศน์เชิงสัมพันธ์ แต่หลาย ๆ คนใช้อยู่ดี นี่คือ "รูปแบบคุณสมบัติ" ที่กล่าวถึงโดยคำตอบอื่น ดูคำถามอื่น ๆ ด้วยแท็ก eavใน StackOverflow สำหรับข้อผิดพลาดบางอย่าง

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


ความคิดเพิ่มเติมเกี่ยวกับ EAV: แม้ว่าหลายคนจะชอบ EAV แต่ฉันก็ไม่ชอบ ดูเหมือนว่าทางออกที่ยืดหยุ่นที่สุดและดีที่สุด แต่เก็บไว้ในใจสุภาษิตTANSTAAFL นี่คือข้อเสียของ EAV:

  • ไม่มีวิธีที่จะทำให้คอลัมน์บังคับ (เทียบเท่าNOT NULL)
  • ไม่มีวิธีใช้ชนิดข้อมูล SQL เพื่อตรวจสอบรายการ
  • ไม่มีวิธีที่จะตรวจสอบให้แน่ใจว่าสะกดชื่อแอตทริบิวต์อย่างสม่ำเสมอ
  • ไม่มีวิธีที่จะวาง foreign key ในค่าของแอตทริบิวต์ที่กำหนดเช่นสำหรับตารางการค้นหา
  • การดึงผลลัพธ์ในรูปแบบตารางแบบดั้งเดิมนั้นซับซ้อนและมีราคาแพงเนื่องจากการรับแอตทริบิวต์จากหลายแถวคุณต้องทำJOINสำหรับแต่ละแอตทริบิวต์

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

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

ฉันจะใช้ EAV เฉพาะเมื่อทุกแถวต้องได้รับอนุญาตให้มีแอตทริบิวต์ที่แตกต่างกัน เมื่อคุณมีประเภทผลิตภัณฑ์ที่ จำกัด แน่นอน EAV จะ overkill Class Table มรดกจะเป็นตัวเลือกแรกของฉัน


อัปเดต 2019: ยิ่งฉันเห็นคนที่ใช้ JSON เป็นโซลูชันสำหรับปัญหา "แอตทริบิวต์ที่กำหนดเองจำนวนมาก" ยิ่งฉันชอบโซลูชันนั้นน้อยลง มันทำให้การสืบค้นซับซ้อนเกินไปแม้ว่าจะใช้ฟังก์ชั่น JSONพิเศษเพื่อรองรับ ใช้พื้นที่เก็บข้อมูลมากขึ้นในการจัดเก็บเอกสาร JSON เมื่อเทียบกับการจัดเก็บในแถวและคอลัมน์ปกติ

โดยทั่วไปไม่มีวิธีแก้ปัญหาใด ๆ ที่ง่ายหรือมีประสิทธิภาพในฐานข้อมูลเชิงสัมพันธ์ แนวคิดทั้งหมดของการมี "แอตทริบิวต์ตัวแปร" เป็นพื้นฐานที่ขัดแย้งกับทฤษฎีเชิงสัมพันธ์

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


11
@HimalayaGarg Option "4.5" เป็นสิ่งที่ตรงกันข้ามกับจุดโพสต์ทั้งหมดของ Bill
user3308043

2
แตกต่างจาก MySQL, SQL Server รองรับ XML, XPath และ XQuery อย่างกว้างขวาง ดังนั้นสำหรับผู้ใช้ SQL Server ตัวเลือกที่ดีที่สุดคือการจัดเก็บคุณสมบัติพิเศษในคอลัมน์ประเภท XML (ตัวเลือก 4) วิธีนี้คุณไม่จำเป็นต้อง "ดึงทั้งหยดกลับไปที่แอปพลิเคชันแล้วเรียงลำดับที่นั่น" คุณสามารถสร้างดัชนีในคอลัมน์ XML ใน SQL Server
Delphi.Boy


2
ฉันชอบ LOB ต่อเนื่องสำหรับกรณีของฉัน แต่มันเหมาะสำหรับออม? ฉันใช้ EF
Mahmood Jenami

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

12

@StoneHeart

ฉันจะไปที่นี่กับ EAV และ MVC ตลอดทาง

@Bill Karvin

นี่คือข้อเสียของ EAV:

  • ไม่มีวิธีในการสร้างคอลัมน์บังคับ (เทียบเท่ากับ NOT NULL)
  • ไม่มีวิธีใช้ชนิดข้อมูล SQL เพื่อตรวจสอบรายการ
  • ไม่มีวิธีที่จะตรวจสอบให้แน่ใจว่าสะกดชื่อแอตทริบิวต์อย่างสม่ำเสมอ
  • ไม่มีวิธีที่จะวาง foreign key ในค่าของแอตทริบิวต์ที่กำหนดเช่นสำหรับตารางการค้นหา

ทุกสิ่งที่คุณพูดถึงที่นี่:

  • การตรวจสอบข้อมูล
  • การตรวจสอบการสะกดชื่อแอตทริบิวต์ชื่อ
  • คอลัมน์ / ฟิลด์บังคับ
  • การจัดการการทำลายของคุณสมบัติที่ต้องพึ่งพา

ในความคิดของฉันไม่ได้อยู่ในฐานข้อมูลเลยเพราะไม่มีฐานข้อมูลใดที่สามารถจัดการกับการโต้ตอบและความต้องการในระดับที่เหมาะสมเช่นเดียวกับภาษาการเขียนโปรแกรมของแอปพลิเคชัน

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

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

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

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

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

หากคุณยังคงกังวลเกี่ยวกับประสิทธิภาพการทำงานของแอปพลิเคชันคุณสามารถใช้ Erlang, C ++, Go Language เพื่อประมวลผลข้อมูลล่วงหน้าและในภายหลังเพียงประมวลผลข้อมูลที่ปรับให้เหมาะสมที่สุดในแอปหลักของคุณ


you can always use Erlang, C++, Go Language to pre-process the dataคุณหมายถึงอะไร? แทนที่จะเป็น DB ให้ใช้ Go lang หรือไม่ คุณช่วยอธิบายรายละเอียดเกี่ยวกับเรื่องนี้ได้ไหม?
Green

1
ฉันเห็นด้วยอย่างยิ่ง EAV เป็นวิธีที่จะไปโดยเฉพาะอย่างยิ่งถ้าคุณต้องการระดับความยืดหยุ่นซึ่งจะช่วยให้คุณสามารถเพิ่มผลิตภัณฑ์และพารามิเตอร์ชนิดใหม่โดยไม่มีการเปลี่ยนแปลง db schema ฉันหมายถึงการผลิตสดผ่านแอปพลิเคชันของคุณ เคยทำมาแล้ว ทำงานให้ฉัน เกี่ยวกับข้อความค้นหาที่ช้า ... มีใครที่นี่เคยได้ยินเกี่ยวกับแคชไหม ;)
pawel.kalisz

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

6

ถ้าฉันใช้Class Table Inheritanceความหมาย:

ตารางหนึ่งสำหรับผลิตภัณฑ์การจัดเก็บแอตทริบิวต์ที่ใช้ร่วมกันได้กับผลิตภัณฑ์ทุกประเภท จากนั้นหนึ่งตารางต่อประเภทผลิตภัณฑ์จัดเก็บแอตทริบิวต์เฉพาะผลิตภัณฑ์ประเภทนั้น - เรียกเก็บเงินจาก Karwin

ซึ่งฉันชอบสิ่งที่ดีที่สุดของคำแนะนำของ Bill Karwin .. ฉันสามารถคาดการณ์ข้อเสียได้หนึ่งข้อซึ่งฉันจะพยายามอธิบายวิธีป้องกันไม่ให้เกิดปัญหา

ฉันควรมีแผนฉุกเฉินอะไรบ้างเมื่อแอตทริบิวต์ที่ใช้ร่วมกันกับ 1 ประเภทเท่านั้นจากนั้นจะกลายเป็นเรื่องธรรมดาถึง 2 จากนั้น 3 และอื่น ๆ

ตัวอย่างเช่น: (นี่เป็นเพียงตัวอย่างไม่ใช่ปัญหาจริงของฉัน)

หากเราขายเฟอร์นิเจอร์เราอาจขายเก้าอี้โคมไฟโซฟาทีวี ฯลฯ ประเภททีวีอาจเป็นประเภทเดียวที่เราพกพาที่มีการใช้พลังงาน ดังนั้นฉันจะใส่แอตทริบิวต์บนpower_consumption tv_type_tableแต่เราเริ่มที่จะพกพาระบบโฮมเธียเตอร์ซึ่งมีpower_consumptionทรัพย์สินด้วย ตกลงเป็นผลิตภัณฑ์อื่นเพียงผลิตภัณฑ์เดียวดังนั้นฉันจะเพิ่มเขตข้อมูลนี้ไปยังstereo_type_tableเช่นกันเนื่องจากอาจเป็นเรื่องที่ง่ายที่สุด ณ จุดนี้ แต่เมื่อเวลาผ่านไปเมื่อเราเริ่มพกพาอุปกรณ์อิเล็คทรอนิคส์มากขึ้นเราก็ตระหนักว่าpower_consumptionมันกว้างพอสมควรmain_product_tableจะเพียงพอในวงกว้างว่ามันควรจะอยู่ในสิ่งที่ฉันควรทำตอนนี้?

main_product_tableเพิ่มข้อมูลไปยัง เขียนสคริปต์ห่วงผ่านอิเล็กทรอนิกส์และใส่ค่าที่ถูกต้องจากกันไปtype_table main_product_tableจากนั้นปล่อยคอลัมน์นั้นจากแต่ละรายการtype_tableรายการ

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


3

คุณสามารถมีตารางผลิตภัณฑ์และตาราง ProductAdditionInfo แยกต่างหากที่มี 3 คอลัมน์: รหัสผลิตภัณฑ์ชื่อข้อมูลเพิ่มเติมค่าข้อมูลเพิ่มเติม หากมีการใช้สีในผลิตภัณฑ์จำนวนมาก แต่ไม่ใช่ทุกชนิดคุณอาจเป็นคอลัมน์ว่างเปล่าในตารางผลิตภัณฑ์หรือเพียงแค่ใส่ไว้ใน ProductAdditionalInfo

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

Steve Yegge เรียกรูปแบบคุณสมบัตินี้และเขียนบทความยาว ๆ เกี่ยวกับการใช้งาน


4
รูปแบบคุณสมบัติเป็นเพียงชื่อคุณสมบัติ - ค่าโดยชื่ออื่น มีการใช้กันอย่างแพร่หลาย แต่เก็บไว้ในฐานข้อมูลเชิงสัมพันธ์แบ่งกฎของการฟื้นฟู
Bill Karwin

2
ตามจริงแล้วเมื่อฉันอ่านคำอธิบายของ EAV ใน @Bills คำตอบฉันไม่เข้าใจสิ่งที่เขาอธิบาย แต่เมื่อคุณพูดว่า3 columns: product ID, additional info name, additional info valueฉันเข้าใจแนวคิด และฉันเคยทำสิ่งนี้มาก่อนและเจอปัญหา อย่างไรก็ตามฉันจำไม่ได้ว่าเกิดปัญหาอะไรขึ้น
JD Isaacks

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