Używasz dziedziczenia (znanego również w modelowaniu relacji encji jako „podklasa” lub „kategoria”). Ogólnie istnieją 3 sposoby przedstawienia go w bazie danych:
- "Wszystkie zajęcia w jednej tabeli": Mieć tylko jedną tabelę „obejmującą” klasę nadrzędną i wszystkie klasy podrzędne (tj. ze wszystkimi kolumnami nadrzędnymi i podrzędnymi), z ograniczeniem CHECK, aby zapewnić, że właściwy podzbiór pól jest inny niż NULL (tj. dwa różne dzieci nie „mieszają się”).
- "Klasa betonu na stół": Miej inny stół dla każdego dziecka, ale nie tabeli rodzica. Wymaga to powtórzenia relacji rodziców (w twoim przypadku Zapasy <- Przechowywanie) u wszystkich dzieci.
- „Klasa na tabelę”: Posiadanie stołu dla rodziców i osobnego stołu dla każdego dziecka, co próbujesz zrobić. Jest to najczystsze, ale może kosztować trochę wydajności (głównie podczas modyfikowania danych, nie tak bardzo podczas wykonywania zapytań, ponieważ możesz dołączyć bezpośrednio od dziecka i pominąć rodzica).
Zwykle wolę trzecie podejście, ale wymuszam obie obecność i wyłączność dziecka na poziomie aplikacji. Egzekwowanie obu na poziomie bazy danych jest nieco kłopotliwe, ale można to zrobić, jeśli DBMS obsługuje ograniczenia odroczone. Na przykład:
CHECK (
(
(VAN_ID IS NOT NULL AND VAN_ID = STORAGE_ID)
AND WAREHOUSE_ID IS NULL
)
OR (
VAN_ID IS NULL
AND (WAREHOUSE_ID IS NOT NULL AND WAREHOUSE_ID = STORAGE_ID)
)
)
Wymusi to zarówno wyłączność (ze względu na CHECK
) i obecność (ze względu na kombinację CHECK
i FK1
/FK2
) dziecka.
Niestety, MS SQL Server nie obsługuje odroczonych ograniczeń, ale możesz być w stanie "ukryć" całą operację za procedurami składowanymi i zabronić klientom bezpośredniej modyfikacji tabel.
Tylko wyłączność może być egzekwowana bez odroczonych ograniczeń:
STORAGE_TYPE
jest rozróżniaczem typów, zwykle liczbą całkowitą w celu zaoszczędzenia miejsca (w powyższym przykładzie 0 i 1 są "znane" aplikacji i odpowiednio interpretowane).
VAN.STORAGE_TYPE
i WAREHOUSE.STORAGE_TYPE
mogą być obliczane (czyli "obliczone"), aby zaoszczędzić miejsce i uniknąć konieczności CHECK
s.
--- EDYTUJ ---
Kolumny obliczeniowe działałyby w SQL Server w ten sposób:
CREATE TABLE STORAGE (
STORAGE_ID int PRIMARY KEY,
STORAGE_TYPE tinyint NOT NULL,
UNIQUE (STORAGE_ID, STORAGE_TYPE)
);
CREATE TABLE VAN (
STORAGE_ID int PRIMARY KEY,
STORAGE_TYPE AS CAST(0 as tinyint) PERSISTED,
FOREIGN KEY (STORAGE_ID, STORAGE_TYPE) REFERENCES STORAGE(STORAGE_ID, STORAGE_TYPE)
);
CREATE TABLE WAREHOUSE (
STORAGE_ID int PRIMARY KEY,
STORAGE_TYPE AS CAST(1 as tinyint) PERSISTED,
FOREIGN KEY (STORAGE_ID, STORAGE_TYPE) REFERENCES STORAGE(STORAGE_ID, STORAGE_TYPE)
);
-- We can make a new van.
INSERT INTO STORAGE VALUES (100, 0);
INSERT INTO VAN VALUES (100);
-- But we cannot make it a warehouse too.
INSERT INTO WAREHOUSE VALUES (100);
-- Msg 547, Level 16, State 0, Line 24
-- The INSERT statement conflicted with the FOREIGN KEY constraint "FK__WAREHOUSE__695C9DA1". The conflict occurred in database "master", table "dbo.STORAGE".
Niestety, SQL Server wymaga kolumny obliczeniowej która jest używana w obcym klucz do UTRZYMYWANIA. Inne bazy danych mogą nie mieć tego ograniczenia (np. wirtualne kolumny Oracle), co może zaoszczędzić trochę miejsca do przechowywania.