Model, do którego link prowadzi, wygląda na częściowy model entity–attribute–value (EAV). EAV jest bardzo elastyczny, ale oferuje słabą integralność danych, jest uciążliwy i zwykle nieefektywny. Nie jest to zgodne z duchem modelu relacyjnego. Pracując w dużych witrynach e-commerce, mogę powiedzieć, że nie jest to standardowa lub dobra praktyka projektowania baz danych w tej dziedzinie.
Jeśli nie masz olbrzymiej liczby typów produktów (do dziesiątek, ale nie setek), możesz sobie z tym poradzić za pomocą jednego z dwóch wspólnych podejść.
Pierwszym podejściem jest po prostu posiadanie pojedynczej tabeli dla produktów, z kolumnami dla wszystkich atrybutów, które mogą być potrzebne dla każdego rodzaju produktu. Używasz tych kolumn, które są odpowiednie dla każdego rodzaju produktu, i pozostawiasz resztę pustą. Załóżmy, że sprzedajesz książki, muzykę i wideo:
create table Product (
id integer primary key,
name varchar(255) not null,
type char(1) not null check (type in ('B', 'M', 'V')),
number_of_pages integer, -- book only
duration_in_seconds integer, -- music and video only
classification varchar(2) check (classification in ('U', 'PG', '12', '15', '18')) -- video only
);
Ma to tę zaletę, że jest proste i nie wymaga złączeń. Jednak nie działa to dobrze na egzekwowanie integralności danych (na przykład można mieć książkę bez liczby stron), a jeśli masz więcej niż kilka rodzajów produktów, stół stanie się bardzo nieporęczny .
Można tynk nad problemem integralności z ograniczeniami kontrolnych na poziomie tabeli, które wymagają każdy rodzaj produktów mają wartości niektórych kolumn, na przykład:
check ((case when type = 'B' then (number_of_pages is not null) else true end)))
(wskazówka kapelusz Joe Čelko tam - i spojrzał w górę jak zrobić logicznej implikacji w SQL i znalazł przykład, w którym robi to z tej konstrukcji do budowy bardzo podobne ograniczenie wyboru)
można nawet powiedzieć:
check ((case when type = 'B' then (number_of_pages is not null) else (number_of_pages is null) end)))
Aby upewnić się, że żaden wiersz nie ma wartości w kolumnie nieodpowiedniej do jej typu.
Drugim podejściem jest użycie wielu tabel: jednej tabeli bazowej zawierającej kolumny wspólne dla wszystkich produktów i jednej tabeli pomocniczej dla każdego typu kolumn zawierających produkty specyficzne dla produktów tego typu.A więc:
create table Product (
id integer primary key,
type char(1) not null check (type in ('B', 'M', 'V')),
name varchar(255) not null
);
create table Book (
id integer primary key references Product,
number_of_pages integer not null
);
create table Music (
id integer primary key references Product,
duration_in_seconds integer not null
);
create table Video (
id integer primary key references Product,
duration_in_seconds integer not null,
classification varchar(2) not null check (classification in ('U', 'PG', '12', '15', '18'))
);
Należy zauważyć, że tabele pomocnicze mają ten sam klucz podstawowy, co tabela główna; ich podstawowa kolumna klucza jest również obcym kluczem do głównej tabeli.
To podejście jest nadal dość proste i lepiej sprawdza się w egzekwowaniu uczciwości. Zapytania będą typowo obejmować przyłącza, choć:
select
p.id,
p.name
from
Product p
join Book b on p.id = b.id
where
b.number_of_pages > 300;
Uczciwość nie jest jeszcze idealny, ponieważ jest możliwe, aby utworzyć wiersz pomocniczy tabelach odpowiadających rzędu niewłaściwego typu w głównej tabeli, lub do tworzenia wierszy wiele tabel pomocniczych odpowiadających pojedynczemu wierszowi w tabeli głównej. Możesz to naprawić, udoskonalając model dalej. Jeśli klucz podstawowy jest kluczem złożonym, który zawiera kolumnę typu, to typ produktu jest osadzony w jego kluczu podstawowym (książka miałaby klucz podstawowy, taki jak ("B", 1001)). Będziesz musiał wprowadzić kolumnę typu w tabelach pomocniczych, aby mogły one mieć klucze obce wskazujące na główną tabelę, a punkt ten mógł dodać ograniczenie sprawdzające w każdej tabeli pomocniczej, która wymaga, aby typ był poprawny. Tak:
create table Product (
type char(1) not null check (type in ('B', 'M', 'V')),
id integer not null,
name varchar(255) not null,
primary key (type, id)
);
create table Book (
type char(1) not null check (type = 'B'),
id integer not null,
number_of_pages integer not null,
primary key (type, id),
foreign key (type, id) references Product
);
To również sprawia, że łatwiej zapytać odpowiednie tabele podane tylko klucz podstawowy - można od razu powiedzieć, jaki rodzaj produktu, który odnosi się bez uprzedniego zapytania do głównego stołu.
Jednak nadal można tworzyć wiersze w głównej tabeli bez odpowiednich wierszy w dowolnej tabeli pomocniczej. Nie wiem od ręki, jak to naprawić.
Istnieje również potencjalny problem duplikowania kolumn - tak jak w powyższym schemacie, w którym kolumna czasu trwania jest powielana w dwóch tabelach. Można to naprawić poprzez wprowadzenie pośrednich tabele pomocnicze dla współdzielonych kolumnach:
create table Media (
type char(1) not null check (type in ('M', 'V')),
id integer not null,
duration_in_seconds integer not null,
primary key (type, id),
foreign key (type, id) references Product
);
create table Music (
type char(1) not null check (type = 'M'),
id integer not null,
primary key (type, id),
foreign key (type, id) references Product
);
create table Video (
type char(1) not null check (type = 'V'),
id integer not null,
classification varchar(2) not null check (classification in ('U', 'PG', '12', '15', '18')),
primary key (type, id),
foreign key (type, id) references Product
);
Nie mogą myśleć, że była warta dodatkowego wysiłku. Jednak to, co można rozważyć robi jest mieszanie tych dwóch podejść (Single stołowy i pomocnicze) do czynienia z tego typu sytuacjach, posiadające wspólny stół dla niektórych podobnych rodzajów produktów:
create table Media (
type char(1) not null check (type in ('M', 'V')),
id integer not null,
duration_in_seconds integer not null,
classification varchar(2) check (classification in ('U', 'PG', '12', '15', '18')),
primary key (type, id),
foreign key (type, id) references Product,
check ((case when type = 'V' then (classification is not null) else (classification is null) end)))
);
który byłby szczególnie przydatny jeśli były podobne produkty, które zostały zgrupowane w aplikacji. W tym przykładzie, jeśli witryna sklepu przedstawia razem audio i wideo, ale oddzielnie dla książek, struktura ta może obsługiwać znacznie bardziej wydajne wyszukiwanie niż oddzielne tabele pomocnicze dla każdego rodzaju nośnika.
Dzięki za pomoc. Ale myślę, że to mi nie pomoże. – user2455135
No cóż, to wstyd. Powodzenia ze znalezieniem rozwiązania! –
@TomAnderson to fascynująca i kompletna odpowiedź. Czy możesz podać więcej informacji, dlaczego nie jesteś fanem projektu encji-wartości atrybutów? Dzięki – mils