Cấu trúc phù hợp cho kịch bản này là mô hình SubClass / Thừa kế và gần giống với khái niệm tôi đề xuất trong câu trả lời này: Danh sách giá trị không đồng nhất theo thứ tự .
Mô hình được đề xuất trong câu hỏi này thực sự khá gần gũi ở chỗ Animal
thực thể chứa loại (tức là race
) và các thuộc tính phổ biến trên tất cả các loại. Tuy nhiên, có hai thay đổi nhỏ cần thiết:
Xóa các trường Cat_ID và Dog_ID khỏi các thực thể tương ứng của chúng:
Các khái niệm then chốt ở đây là tất cả mọi thứ là một Animal
, không phân biệt race
: Cat
, Dog
, Elephant
, và vân vân. Cho rằng điểm bắt đầu, bất kỳ đặc biệt race
của Animal
không thực sự cần một định danh riêng biệt từ:
- sự
Animal_ID
độc đáo
- các
Cat
, Dog
và bất kỳ thêm race
các đơn vị bổ sung trong tương lai không, bởi bản thân, đầy đủ đại diện cho bất kỳ đặc biệt Animal
; chúng chỉ có ý nghĩa khi được sử dụng kết hợp với thông tin có trong thực thể mẹ , Animal
.
Do đó, Animal_ID
bất động sản trong Cat
, Dog
, vv thực thể là cả PK và mặt sau FK đến Animal
thực thể.
Phân biệt giữa các loại breed
:
Chỉ vì hai thuộc tính có cùng tên không nhất thiết có nghĩa là các thuộc tính đó giống nhau, ngay cả khi tên giống nhau ngụ ý mối quan hệ như vậy. Trong trường hợp này, những gì bạn thực sự phải là thực sự CatBreed
và DogBreed
là "loại" riêng biệt
Ghi chú ban đầu
- SQL dành riêng cho Microsoft SQL Server (tức là T-SQL). Có nghĩa là, hãy cẩn thận về các kiểu dữ liệu vì chúng không giống nhau trên tất cả các RDBMS. Ví dụ, tôi đang sử dụng
VARCHAR
nhưng nếu bạn cần lưu trữ bất cứ thứ gì bên ngoài bộ ASCII tiêu chuẩn, bạn thực sự nên sử dụng NVARCHAR
.
- Các trường ID của các bảng "loại" (
Race
, CatBreed
và DogBreed
) không tự động tăng (nghĩa là IDENTITY theo T-SQL) vì chúng là các hằng số ứng dụng (nghĩa là chúng là một phần của ứng dụng) là các giá trị tra cứu tĩnh trong cơ sở dữ liệu và được biểu diễn dưới dạng enum
s trong C # (hoặc các ngôn ngữ khác). Nếu các giá trị được thêm vào, chúng được thêm vào trong các tình huống được kiểm soát. Tôi bảo lưu việc sử dụng các trường tăng tự động cho dữ liệu người dùng đi qua ứng dụng.
- Quy ước đặt tên tôi sử dụng là đặt tên cho mỗi bảng lớp con bắt đầu bằng tên lớp chính theo sau là tên lớp con. Điều này giúp tổ chức các bảng cũng như chỉ ra rõ ràng (không cần nhìn vào FK) mối quan hệ của bảng lớp con với bảng thực thể chính.
- Vui lòng xem phần "Chỉnh sửa cuối cùng" ở cuối để biết ghi chú về Lượt xem.
"Giống" là "Chủng tộc" - Cách tiếp cận cụ thể
Nhóm bảng đầu tiên này là bảng tra cứu / loại:
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
);
Danh sách thứ hai này là thực thể "Động vật" chính:
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);
Điều này thiết lập thứ ba của bảng là các thực thể sub-class miễn phí mà hoàn thành định nghĩa của mỗi Race
của 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);
Mô hình sử dụng breed
loại chia sẻ được hiển thị sau phần "Ghi chú bổ sung".
Ghi chú bổ sung
- Khái niệm
breed
dường như là một tiêu điểm cho sự nhầm lẫn. Nó được đề xuất bởi jcolebrand (trong một bình luận về câu hỏi) đó breed
là một tài sản được chia sẻ trên các race
s khác nhau và hai câu trả lời khác đã được tích hợp như vậy trong các mô hình của họ. Tuy nhiên, đây là một lỗi vì các giá trị breed
không được chia sẻ trên các giá trị khác nhau của race
. Có, tôi biết rằng hai mô hình đề xuất khác cố gắng giải quyết vấn đề này bằng cách làm race
cha mẹ của breed
. Mặc dù về mặt kỹ thuật giải quyết được vấn đề về mối quan hệ, nó không giúp giải quyết câu hỏi mô hình hóa tổng thể về những việc cần làm đối với các thuộc tính không phổ biến cũng như cách xử lý một race
vấn đề không có breed
. Nhưng, trong trường hợp một tài sản như vậy được đảm bảo tồn tại trên tất cảAnimal
s, tôi cũng sẽ bao gồm một tùy chọn cho điều đó (bên dưới).
- Các mô hình được đề xuất bởi vijayp và DavidN (dường như giống hệt nhau) không hoạt động vì:
- Họ hoặc
- không cho phép các thuộc tính không phổ biến được lưu trữ (ít nhất là không cho các trường hợp riêng lẻ của bất kỳ
Animal
), hoặc
- yêu cầu tất cả các thuộc tính cho tất cả các
race
s được lưu trữ trong Animal
thực thể đó là một cách rất đơn giản (và gần như không liên quan) để biểu diễn dữ liệu này. Có, mọi người làm điều này mọi lúc, nhưng điều đó có nghĩa là có nhiều trường NULL trên mỗi hàng cho các thuộc tính không dành cho cụ thể đó race
và biết trường nào trên mỗi hàng được liên kết với cụ thể race
của bản ghi đó.
- Họ không cho phép thêm một
race
số Animal
trong tương lai mà không có breed
như một tài sản. Và thậm chí nếu TẤT CẢ Animal
s có một breed
, điều đó sẽ không thay đổi cấu trúc do những gì trước đây đã được ghi nhận về breed
: đó breed
là phụ thuộc vào race
(tức là breed
cho Cat
không phải là điều tương tự như breed
cho Dog
).
"Giống" như cách tiếp cận tài sản chung / / chung
Xin lưu ý:
SQL bên dưới có thể được chạy trong cùng một cơ sở dữ liệu như mô hình được trình bày ở trên:
- Cái
Race
bàn giống nhau
- Cái
Breed
bàn mới
- Ba
Animal
bảng đã được thêm vào một2
- Ngay cả
Breed
khi là một tài sản chung hiện nay, có vẻ như không được Race
lưu ý trong thực thể chính / cha mẹ (ngay cả khi nó đúng về mặt kỹ thuật). Vì vậy, cả hai RaceID
và BreedID
được đại diện trong Animal2
. Để ngăn chặn sự không phù hợp giữa RaceID
ghi chú trong Animal2
và BreedID
khác biệt RaceID
, tôi đã thêm một FK trên cả hai RaceID, BreedID
tham chiếu CONSTRAINT ĐỘC ĐÁO của các trường đó trong Breed
bảng. Tôi thường coi thường việc chỉ ra một FK cho một CONSTRAINT ĐỘC ĐÁO, nhưng đây là một trong số ít lý do hợp lệ để làm điều đó. CONSTRAINT ĐỘC ĐÁO là một "Khóa thay thế" một cách hợp lý, làm cho nó hợp lệ cho việc sử dụng này. Cũng xin lưu ý rằng Breed
bảng vẫn có PK BreedID
.
- Lý do không chỉ xảy ra với PK trên các trường kết hợp và không có CONSTRAINT UNIITE là vì nó sẽ cho phép
BreedID
lặp lại cùng một giá trị khác nhau RaceID
.
- Lý do không chuyển đổi PK và UNIQUE CONSTRAINT xung quanh là vì đây có thể không phải là cách sử dụng duy nhất
BreedID
, vì vậy vẫn có thể tham chiếu một giá trị cụ thể Breed
mà không có RaceID
sẵn.
- Mặc dù mô hình sau không hoạt động, nó có hai lỗ hổng tiềm năng liên quan đến khái niệm chia sẻ
Breed
(và đó là lý do tại sao tôi thích các bảng đặc Race
thù Breed
).
- Có một giả định ngầm định rằng TẤT CẢ các giá trị
Breed
có cùng thuộc tính. Không có cách dễ dàng nào trong mô hình này để có các thuộc tính khác nhau giữa Dog
"giống" và Elephant
"giống". Tuy nhiên, vẫn có một cách để làm điều này, được ghi chú trong phần "Chỉnh sửa cuối cùng".
- Không có cách nào để chia sẻ
Breed
nhiều hơn một chủng tộc. Tôi không chắc chắn nếu điều đó là mong muốn để làm (hoặc có thể không phải trong khái niệm động vật nhưng có thể trong các tình huống khác sẽ sử dụng loại mô hình này), nhưng không thể ở đây.
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);
Chỉnh sửa cuối cùng (hy vọng ;-)
- Về khả năng (và khó khăn sau đó) xử lý các thuộc tính khác nhau giữa các loại
Breed
, nó là có thể sử dụng các lớp con / thừa kế khái niệm tương tự nhưng với Breed
là thực thể chính. Trong thiết lập này, Breed
bảng sẽ có các thuộc tính chung cho tất cả các loại Breed
(giống như Animal
bảng) và RaceID
sẽ đại diện cho loại Breed
(giống như trong Animal
bảng). Sau đó, bạn sẽ có các bảng lớp con như BreedCat
, BreedDog
v.v. Đối với các dự án nhỏ hơn, điều này có thể được coi là "kỹ thuật quá mức", nhưng nó đang được đề cập như là một lựa chọn cho các tình huống sẽ được hưởng lợi từ nó.
Đối với cả hai cách tiếp cận, đôi khi nó giúp tạo Chế độ xem dưới dạng rút gọn cho các thực thể đầy đủ. Ví dụ: xem xét:
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"
- Mặc dù không phải là một phần của các thực thể logic, nhưng điều khá phổ biến là có các trường kiểm toán trong các bảng để ít nhất có được ý nghĩa khi các bản ghi được chèn và cập nhật. Vì vậy, trong điều kiện thực tế:
- Một
CreatedDate
trường sẽ được thêm vào Animal
bảng. Trường này không cần thiết trong bất kỳ bảng lớp con nào (ví dụ AnimalCat
) vì các hàng được chèn cho cả hai bảng phải được thực hiện cùng một lúc trong một giao dịch.
- Một
LastModifiedDate
trường sẽ được thêm vào Animal
bảng và tất cả các bảng lớp con. Trường này chỉ được cập nhật nếu bảng cụ thể đó được cập nhật: nếu một bản cập nhật xảy ra AnimalCat
nhưng không có trong Animal
một cụ thể AnimalID
, thì chỉ có LastModifiedDate
trường trong AnimalCat
đó sẽ được đặt.