โครงสร้างที่เหมาะสมสำหรับสถานการณ์นี้เป็นรูปแบบ subclass / มรดกและเกือบจะเหมือนกับแนวคิดที่ผมนำเสนอในคำตอบนี้: วิวิธรายการสั่งซื้อของมูลค่า
แบบจำลองที่เสนอในคำถามนี้เป็นจริงค่อนข้างใกล้ที่Animal
เอนทิตีประกอบด้วยประเภท (เช่นrace
) และคุณสมบัติที่พบได้ทั่วไปในทุกประเภท อย่างไรก็ตามมีการเปลี่ยนแปลงเล็กน้อยสองประการที่จำเป็น:
ลบฟิลด์ Cat_ID และ Dog_ID จากเอนทิตีที่เกี่ยวข้อง:
แนวคิดที่สำคัญที่นี่คือว่าทุกอย่างเป็นAnimal
โดยไม่คำนึงถึงrace
: Cat
, Dog
, Elephant
และอื่น ๆ ระบุว่าจุดเริ่มต้นในด้านrace
ของการAnimal
ไม่ได้ต้องการอย่างแท้จริงตัวระบุที่แยกจากกันตั้งแต่:
Animal_ID
เป็นเอกลักษณ์
Cat
, Dog
และอื่น ๆ ที่race
หน่วยงานเพิ่มในอนาคตไม่ได้ด้วยตัวเองแทนใด ๆ โดยเฉพาะอย่างเต็มที่Animal
; Animal
พวกเขาเท่านั้นที่มีความหมายเมื่อใช้ร่วมกับข้อมูลที่มีอยู่ในกิจการที่ผู้ปกครอง
ดังนั้นAnimal_ID
ทรัพย์สินในCat
, Dog
ฯลฯ หน่วยงานที่เป็นทั้ง PK และ FK กลับไปที่Animal
นิติบุคคล
แยกความแตกต่างระหว่างประเภทของbreed
:
เพียงเพราะคุณสมบัติสองรายการที่ใช้ชื่อเดียวกันไม่ได้แปลว่าคุณสมบัติเหล่านั้นเหมือนกันแม้ว่าชื่อนั้นจะมีความหมายเหมือนกันก็ตาม ในกรณีนี้สิ่งที่คุณมีอยู่จริงCatBreed
และDogBreed
แยก "ประเภท"
หมายเหตุเบื้องต้น
- SQL มีเฉพาะกับ Microsoft SQL Server (เช่น T-SQL) ควรระวังเกี่ยวกับประเภทข้อมูลเนื่องจากไม่เหมือนกันใน RDBMS ทั้งหมด ยกตัวอย่างเช่นผมใช้
VARCHAR
แต่ถ้าคุณต้องออกไปข้างนอกร้านอะไรของชุด ASCII NVARCHAR
มาตรฐานคุณจริงๆควรใช้
- ฟิลด์ ID ของ "พิมพ์" ตาราง (
Race
, CatBreed
และDogBreed
) จะไม่อัตโนมัติที่เพิ่มขึ้น (IDENTITY คือในแง่ของ T-SQL) เพราะพวกเขามีค่าคงที่แอพลิเคชัน (กล่าวคือพวกเขาเป็นส่วนหนึ่งของโปรแกรม) ที่มีค่าการค้นหาแบบคงที่ใน ฐานข้อมูลและแสดงเป็นenum
s ใน C # (หรือภาษาอื่น ๆ ) หากมีการเพิ่มค่าพวกเขาจะถูกเพิ่มในสถานการณ์ที่ควบคุม ฉันขอสงวนการใช้ฟิลด์การเพิ่มอัตโนมัติสำหรับข้อมูลผู้ใช้ที่เข้ามาทางแอปพลิเคชัน
- หลักการตั้งชื่อที่ฉันใช้คือตั้งชื่อตารางย่อยแต่ละคลาสที่ขึ้นต้นด้วยชื่อคลาสหลักตามด้วยชื่อคลาสย่อย สิ่งนี้จะช่วยจัดระเบียบตารางรวมทั้งบ่งบอกอย่างชัดเจน (โดยไม่ต้องดูที่ FKs) ความสัมพันธ์ของตารางคลาสย่อยกับตารางเอนทิตีหลัก
- โปรดดูส่วน "การแก้ไขขั้นสุดท้าย" ในตอนท้ายเพื่อรับทราบเกี่ยวกับการดู
"Breed" เป็น "Race" - วิธีการเฉพาะ
ชุดแรกของตารางนี้เป็นตารางการค้นหา / ประเภท:
CREATE TABLE Race
(
RaceID INT NOT NULL PRIMARY KEY
RaceName VARCHAR(50) NOT NULL
);
CREATE TABLE CatBreed
(
CatBreedID INT NOT NULL PRIMARY KEY,
BreedName VARCHAR(50),
CatBreedAttribute1 INT,
CatBreedAttribute2 VARCHAR(10)
-- other "CatBreed"-specific properties as needed
);
CREATE TABLE DogBreed
(
DogBreedID INT NOT NULL PRIMARY KEY,
BreedName VARCHAR(50),
DogBreedAttribute1 TINYINT
-- other "DogBreed"-specific properties as needed
);
รายชื่อที่สองนี้เป็นนิติบุคคล "สัตว์" หลัก:
CREATE TABLE Animal
(
AnimalID INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
RaceID INT NOT NULL, -- FK to Race
Name VARCHAR(50)
-- other "Animal" properties that are shared across "Race" types
);
ALTER TABLE Animal
ADD CONSTRAINT [FK_Animal_Race]
FOREIGN KEY (RaceID)
REFERENCES Race (RaceID);
นี้ชุดที่สามของตารางเป็นหน่วยงานย่อยระดับฟรีที่เสร็จสิ้นความหมายของแต่ละRace
ของAnimal
:
CREATE TABLE AnimalCat
(
AnimalID INT NOT NULL PRIMARY KEY, -- FK to Animal
CatBreedID INT NOT NULL, -- FK to CatBreed
HairColor VARCHAR(50) NOT NULL
-- other "Cat"-specific properties as needed
);
ALTER TABLE AnimalCat
ADD CONSTRAINT [FK_AnimalCat_CatBreed]
FOREIGN KEY (CatBreedID)
REFERENCES CatBreed (CatBreedID);
ALTER TABLE AnimalCat
ADD CONSTRAINT [FK_AnimalCat_Animal]
FOREIGN KEY (AnimalID)
REFERENCES Animal (AnimalID);
CREATE TABLE AnimalDog
(
AnimalID INT NOT NULL PRIMARY KEY, -- FK to Animal
DogBreedID INT NOT NULL, -- FK to DogBreed
HairColor VARCHAR(50) NOT NULL
-- other "Dog"-specific properties as needed
);
ALTER TABLE AnimalDog
ADD CONSTRAINT [FK_AnimalDog_DogBreed]
FOREIGN KEY (DogBreedID)
REFERENCES DogBreed (DogBreedID);
ALTER TABLE AnimalDog
ADD CONSTRAINT [FK_AnimalDog_Animal]
FOREIGN KEY (AnimalID)
REFERENCES Animal (AnimalID);
รูปแบบที่ใช้breed
ประเภทที่แชร์จะปรากฏหลังส่วน "หมายเหตุเพิ่มเติม"
หมายเหตุเพิ่มเติม
- แนวคิดของการ
breed
ดูเหมือนจะเป็นจุดโฟกัสสำหรับความสับสน มันได้รับการแนะนำโดย jcolebrand (ในความคิดเห็นเกี่ยวกับคำถาม) ซึ่งbreed
เป็นคุณสมบัติที่ใช้ร่วมกันในหลาย ๆrace
s และอีกสองคำตอบก็รวมอยู่ในแบบจำลองของพวกเขาเช่นกัน นี้เป็นความผิดพลาด แต่เนื่องจากค่าสำหรับไม่ได้ใช้ร่วมกันในค่าที่แตกต่างกันของbreed
race
ใช่ฉันทราบว่าทั้งสองรุ่นที่นำเสนออื่น ๆ พยายามที่จะแก้ปัญหานี้โดยการทำให้ผู้ปกครองของrace
breed
ในขณะที่ในทางเทคนิคแก้ปัญหาความสัมพันธ์ก็ไม่ได้ช่วยแก้ปัญหาคำถามการสร้างแบบจำลองโดยรวมของสิ่งที่ต้องทำเกี่ยวกับอสังหาริมทรัพย์ที่ไม่ใช่ที่พบบ่อยหรือวิธีการจัดการที่ไม่ได้มีrace
breed
แต่ในกรณีที่ทรัพย์สินดังกล่าวรับประกันว่าจะมีอยู่ทั้งหมดAnimal
s ฉันจะรวมตัวเลือกสำหรับสิ่งนั้นด้วย (ด้านล่าง)
- โมเดลที่เสนอโดย vijayp และ DavidN (ซึ่งดูเหมือนจะเหมือนกัน) ไม่ทำงานเพราะ:
- พวกเขาทั้งสอง
- ไม่อนุญาตให้มีการจัดเก็บคุณสมบัติที่ไม่ทั่วไป (อย่างน้อยก็ไม่ใช่สำหรับแต่ละอินสแตนซ์ของใด ๆ
Animal
) หรือ
- ต้องการให้คุณสมบัติทั้งหมดสำหรับทุก
race
s ถูกเก็บไว้ในAnimal
เอนทิตีซึ่งเป็นวิธีที่เรียบมาก (และเกือบจะไม่สัมพันธ์กัน) ในการแสดงข้อมูลนี้ ใช่ผู้คนทำสิ่งนี้ตลอดเวลา แต่มันหมายถึงการมีเขตข้อมูล NULL จำนวนมากต่อแถวสำหรับคุณสมบัติที่ไม่ได้มีไว้สำหรับเฉพาะนั้นrace
และการรู้ว่าเขตข้อมูลต่อแถวเกี่ยวข้องกับrace
ระเบียนใดระเบียนหนึ่ง
- พวกเขาไม่อนุญาตให้มีการเพิ่ม
race
ของAnimal
ในอนาคตที่ไม่ได้breed
เป็นทรัพย์สิน และแม้ว่า ALL Animal
s มีbreed
ที่จะไม่เปลี่ยนโครงสร้างเนื่องจากสิ่งที่ได้รับการบันทึกไว้ก่อนหน้านี้เกี่ยวกับbreed
: ที่breed
ขึ้นอยู่กับrace
(เช่นbreed
สำหรับการCat
ไม่ได้เป็นสิ่งเดียวกับbreed
สำหรับDog
)
"พันธุ์" เป็นวิธีการทั่วไป / ใช้ร่วมกัน - ทรัพย์สิน
โปรดทราบ:
SQL ด้านล่างสามารถทำงานในฐานข้อมูลเดียวกับรุ่นที่แสดงด้านบน:
Race
ตารางเดียวกัน
Breed
ตารางใหม่
Animal
ตารางทั้งสามได้รับการต่อท้ายด้วย2
- แม้ว่า
Breed
จะเป็นทรัพย์สินทั่วไปในตอนนี้ก็ดูเหมือนจะไม่ถูกต้องที่จะไม่ได้Race
บันทึกไว้ในเอนทิตีหลัก / ผู้ปกครอง (แม้ว่ามันจะถูกต้องทางเทคนิคสัมพันธ์) ดังนั้นทั้งสองRaceID
และBreedID
มีการแสดงสิ่งAnimal2
ต่อไปนี้ เพื่อป้องกันไม่ให้เกิดความไม่ตรงกันระหว่างที่RaceID
ระบุไว้ในAnimal2
และ a BreedID
ที่เป็นของที่แตกต่างกันRaceID
ฉันได้เพิ่ม FK ลงบนทั้งสองRaceID, BreedID
ที่อ้างถึงข้อ จำกัด เฉพาะของเขตข้อมูลเหล่านั้นในBreed
ตาราง ฉันมักจะดูถูก FK ชี้ไปที่ข้อ จำกัด ที่ไม่เหมือนใคร แต่นี่คือหนึ่งในเหตุผลที่ถูกต้อง ข้อ จำกัด ที่ไม่ซ้ำกันคือ "คีย์สำรอง" ในเชิงตรรกะซึ่งทำให้ถูกต้องสำหรับการใช้งานนี้ โปรดทราบว่าBreed
ตารางยังคงมี PK BreedID
บนเพียง
- เหตุผลที่ไม่ได้ไปมีเพียง PK สาขารวมกันและไม่มีข้อ จำกัด ที่ไม่ซ้ำกันก็คือว่ามันจะช่วยให้การเดียวกันจะซ้ำข้ามค่าที่แตกต่างของ
BreedID
RaceID
- เหตุผลที่ไม่เปลี่ยนที่ PK และไม่ซ้ำกันข้อ จำกัด รอบคือว่าอาจนี้ไม่สามารถใช้งานเพียงคนเดียว
BreedID
ดังนั้นจึงควรจะยังคงเป็นไปได้ที่จะอ้างอิงค่าเฉพาะของBreed
โดยไม่ต้องRaceID
ใช้ได้
- ในขณะที่รุ่นต่อไปนี้ใช้งานได้ แต่มีข้อบกพร่องที่อาจเกิดขึ้นสองประการเกี่ยวกับแนวคิดของการแชร์
Breed
(และเป็นเหตุผลที่ฉันชอบตารางที่Race
เฉพาะเจาะจงBreed
)
- มีข้อสมมติโดยนัยที่ค่าทั้งหมดของ
Breed
มีคุณสมบัติเหมือนกันคือ ไม่มีวิธีที่ง่ายในรุ่นนี้ที่จะมีคุณสมบัติที่แตกต่างกันระหว่างDog
"สายพันธุ์" และElephant
"สายพันธุ์" อย่างไรก็ตามยังมีวิธีการทำเช่นนี้ซึ่งถูกบันทึกไว้ในส่วน "การแก้ไขขั้นสุดท้าย"
- ไม่มีทางที่จะแบ่งปันการ
Breed
แข่งขันมากกว่าหนึ่งการแข่งขัน ฉันไม่แน่ใจว่าเป็นสิ่งที่พึงทำหรือไม่ (หรืออาจไม่ได้อยู่ในแนวคิดของสัตว์ แต่อาจอยู่ในสถานการณ์อื่นที่จะใช้แบบจำลองประเภทนี้) แต่ไม่สามารถทำได้ที่นี่
CREATE TABLE Race
(
RaceID INT NOT NULL PRIMARY KEY,
RaceName VARCHAR(50) NOT NULL
);
CREATE TABLE Breed
(
BreedID INT NOT NULL PRIMARY KEY,
RaceID INT NOT NULL, -- FK to Race
BreedName VARCHAR(50)
);
ALTER TABLE Breed
ADD CONSTRAINT [UQ_Breed]
UNIQUE (RaceID, BreedID);
ALTER TABLE Breed
ADD CONSTRAINT [FK_Breed_Race]
FOREIGN KEY (RaceID)
REFERENCES Race (RaceID);
CREATE TABLE Animal2
(
AnimalID INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
RaceID INT NOT NULL, -- FK to Race, FK to Breed
BreedID INT NOT NULL, -- FK to Breed
Name VARCHAR(50)
-- other properties common to all "Animal" types
);
ALTER TABLE Animal2
ADD CONSTRAINT [FK_Animal2_Race]
FOREIGN KEY (RaceID)
REFERENCES Race (RaceID);
-- This FK points to the UNIQUE CONSTRAINT on Breed, _not_ to the PK!
ALTER TABLE Animal2
ADD CONSTRAINT [FK_Animal2_Breed]
FOREIGN KEY (RaceID, BreedID)
REFERENCES Breed (RaceID, BreedID);
CREATE TABLE AnimalCat2
(
AnimalID INT NOT NULL PRIMARY KEY, -- FK to Animal
HairColor VARCHAR(50) NOT NULL
);
ALTER TABLE AnimalCat2
ADD CONSTRAINT [FK_AnimalCat2_Animal2]
FOREIGN KEY (AnimalID)
REFERENCES Animal2 (AnimalID);
CREATE TABLE AnimalDog2
(
AnimalID INT NOT NULL PRIMARY KEY,
HairColor VARCHAR(50) NOT NULL
);
ALTER TABLE AnimalDog2
ADD CONSTRAINT [FK_AnimalDog2_Animal2]
FOREIGN KEY (AnimalID)
REFERENCES Animal2 (AnimalID);
แก้ไขครั้งสุดท้าย (หวังว่า ;-)
- เกี่ยวกับความเป็นไปได้ (และความยาก) ของการจัดการคุณสมบัติที่แตกต่างกันระหว่างประเภทของ
Breed
มันเป็นไปได้ที่จะใช้แนวคิด subclass / มรดกเดียวกัน แต่Breed
เป็นหน่วยงานหลัก ในการตั้งค่านี้Breed
ตารางจะมีคุณสมบัติทั่วไปสำหรับทุกประเภทBreed
(เช่นเดียวกับAnimal
ตาราง) และRaceID
จะแสดงถึงประเภทของBreed
(เหมือนกับที่ทำในAnimal
ตาราง) แล้วคุณจะมีตาราง subclass เช่นBreedCat
, BreedDog
และอื่น ๆ สำหรับโครงการขนาดเล็กสิ่งนี้อาจถูกพิจารณาว่าเป็น "over-engineering" แต่มันถูกกล่าวถึงว่าเป็นตัวเลือกสำหรับสถานการณ์ที่จะได้รับประโยชน์จากมัน
สำหรับทั้งสองวิธีบางครั้งช่วยในการสร้าง Views เป็นทางลัดไปยังเอนทิตีแบบเต็ม ตัวอย่างเช่นพิจารณา:
CREATE VIEW Cats AS
SELECT an.AnimalID,
an.RaceID,
an.Name,
-- other "Animal" properties that are shared across "Race" types
cat.CatBreedID,
cat.HairColor
-- other "Cat"-specific properties as needed
FROM Animal an
INNER JOIN AnimalCat cat
ON cat.AnimalID = an.AnimalID
-- maybe add in JOIN(s) and field(s) for "Race" and/or "Breed"
- แม้ว่าจะไม่ได้เป็นส่วนหนึ่งของเอนทิตีแบบลอจิคัล แต่ก็เป็นเรื่องปกติที่จะมีเขตข้อมูลการตรวจสอบในตารางอย่างน้อยที่สุดจะได้รับความรู้สึกเมื่อมีการแทรกและปรับปรุงระเบียน ดังนั้นในแง่การปฏิบัติ:
CreatedDate
ฟิลด์จะถูกเพิ่มเข้าไปในAnimal
ตาราง ฟิลด์นี้ไม่จำเป็นในตารางย่อยใด ๆ (เช่นAnimalCat
) เนื่องจากแถวที่ถูกแทรกสำหรับตารางทั้งสองควรทำพร้อมกันภายในธุรกรรม
LastModifiedDate
ฟิลด์จะถูกเพิ่มเข้าไปในAnimal
ตารางและตารางคลาสย่อยทั้งหมด ฟิลด์นี้ได้รับการอัปเดตเฉพาะในกรณีที่มีการอัปเดตตารางโดยเฉพาะ: หากมีการอัปเดตเกิดขึ้นAnimalCat
แต่ไม่ได้อยู่ในAnimal
เฉพาะจะมีการตั้งค่าAnimalID
เฉพาะLastModifiedDate
ฟิลด์ที่AnimalCat
ระบุ