Jak efektywnie modelować dziedziczenie w bazie danych?

139

Jakie są najlepsze praktyki dotyczące modelowania dziedziczenia w bazach danych?

Jakie są kompromisy (np. Wątpliwość)?

(Najbardziej interesują mnie SQL Server i .NET, ale chcę też zrozumieć, jak inne platformy rozwiązują ten problem).

Nawet Mien
źródło
14
Jeśli interesuje Cię „najlepsze praktyki”, większość odpowiedzi jest po prostu niepoprawnych. Najlepsza praktyka mówi, że RDb i aplikacja są niezależne; mają zupełnie inne kryteria projektowe. Dlatego „modelowanie dziedziczenia” w bazie danych (lub modelowanie RDb tak, aby pasował do pojedynczej aplikacji lub języka aplikacji) jest bardzo złą praktyką, jest niedoinformowane i łamie podstawowe zasady projektowania RDb i je kaleczy.
PerformanceDBA
6
@PerformanceDBA Jaka jest więc twoja sugestia, aby uniknąć dziedziczenia w modelu DB? Powiedzmy, że mamy 50 różnych typów nauczycieli i chcemy połączyć tego konkretnego nauczyciela z klasą. Jak byś to osiągnął bez dziedziczenia?
svlada
1
@svlada. Jest to łatwe do zaimplementowania w RDb, więc wymagane jest „dziedziczenie”. Zadaj pytanie, dołącz definicję tabeli i przykład, a szczegółowo odpowiem. Jeśli zrobisz to w warunkach OO, będzie to królewski bałagan.
PerformanceDBA

Odpowiedzi:

168

Istnieje kilka sposobów modelowania dziedziczenia w bazie danych. Wybór zależy od Twoich potrzeb. Oto kilka opcji:

Tabela według typu (TPT)

Każda klasa ma własną tabelę. Klasa bazowa zawiera wszystkie elementy klasy bazowej, a każda klasa, która z niej pochodzi, ma własną tabelę z kluczem podstawowym, który jest także kluczem obcym do tabeli klas bazowych; klasa tabeli pochodnej zawiera tylko różne elementy.

Na przykład:

class Person {
    public int ID;
    public string FirstName;
    public string LastName;
}

class Employee : Person {
    public DateTime StartDate;
}

Dałoby to tabele takie jak:

table Person
------------
int id (PK)
string firstname
string lastname

table Employee
--------------
int id (PK, FK)
datetime startdate

Tabela według hierarchii (TPH)

Istnieje pojedyncza tabela, która reprezentuje całą hierarchię dziedziczenia, co oznacza, że ​​kilka kolumn będzie prawdopodobnie rzadkich. Dodawana jest kolumna dyskryminatora, która informuje system, jaki to jest typ wiersza.

Biorąc pod uwagę powyższe klasy, otrzymujesz następującą tabelę:

table Person
------------
int id (PK)
int rowtype (0 = "Person", 1 = "Employee")
string firstname
string lastname
datetime startdate

Dla wszystkich wierszy, które mają typ wiersza 0 (osoba), data początkowa zawsze będzie miała wartość null.

Stół na beton (TPC)

Każda klasa ma własną, w pełni uformowaną tabelę bez odwołań do innych tabel.

Biorąc pod uwagę powyższe klasy, otrzymujesz następujące tabele:

table Person
------------
int id (PK)
string firstname
string lastname

table Employee
--------------
int id (PK)
string firstname
string lastname
datetime startdate
Brad Wilson
źródło
26
„To, co wybierzesz, zależy od twoich potrzeb” - proszę wyjaśnić, ponieważ moim zdaniem powody wyborów stanowią sedno pytania.
Alex,
12
Zobacz mój komentarz do pytania. Używanie zabawnych nowych nazw dla istniejących terminów technicznych Rdb prowadzi do nieporozumień. „TPT” jest podtypem nadrzędnym. „TPH” to Unormalised, poważny błąd. „TPH” jest jeszcze mniej znormalizowany, co jest kolejnym poważnym błędem.
PerformanceDBA
46
Tylko DBA założyłby, że denormalizacja jest zawsze błędem. :)
Brad Wilson
7
Chociaż przyznam, że denormalizacja skutkuje wzrostem wydajności w niektórych przypadkach, jest to całkowicie spowodowane niepełnym (lub nieistniejącym) rozdziałem między logiczną i fizyczną strukturą danych w DBMS. Niestety większość komercyjnych systemów DBMS cierpi na ten problem. @PerformanceDBA jest poprawne. Niedernormalizacja to błąd oceny, poświęcający spójność danych dla szybkości. Niestety, jest to wybór, którego DBA lub deweloper nigdy nie musiałby podejmować, gdyby DBMS został odpowiednio zaprojektowany. Dla przypomnienia, nie jestem administratorem.
Kenneth Cochran
6
@Brad Wilson. Tylko programista przeprowadziłby denormalizację „pod kątem wydajności” lub w inny sposób. Często nie jest to denormalizacja, prawda jest taka, że ​​jest unormalizowana. To, że zdenormalizowanie lub nienormalizacja jest błędem, faktem popartym teorią i doświadczanym przez miliony, nie jest to „domniemanie”.
PerformanceDBA
139

Właściwy projekt bazy danych w niczym nie przypomina odpowiedniego projektu obiektu.

Jeśli planujesz używać bazy danych do czegoś innego niż po prostu serializowanie obiektów (takich jak raporty, zapytania, użycie wielu aplikacji, analiza biznesowa itp.), To nie polecam żadnego prostego mapowania obiektów na tabele.

Wiele osób myśli o wierszu w tabeli bazy danych jako o encji (spędziłem wiele lat myśląc w tych terminach), ale wiersz nie jest bytem. To propozycja. Relacja z bazą danych (tj. Tabela) przedstawia pewne stwierdzenie dotyczące świata. Obecność wiersza wskazuje, że fakt jest prawdziwy (i odwrotnie, jego brak wskazuje, że fakt jest fałszywy).

Z tym zrozumieniem można zauważyć, że pojedynczy typ w programie zorientowanym obiektowo może być przechowywany w kilkunastu różnych relacjach. Różne typy (połączone przez dziedziczenie, asocjację, agregację lub całkowicie niepowiązane) mogą być częściowo przechowywane w jednej relacji.

Najlepiej zadać sobie pytanie, jakie fakty chcesz przechowywać, na jakie pytania będziesz potrzebować odpowiedzi, jakie raporty chcesz generować.

Po utworzeniu odpowiedniego projektu bazy danych tworzenie zapytań / widoków, które umożliwią serializację obiektów do tych relacji, jest prostą sprawą.

Przykład:

W systemie rezerwacji hotelowych może być konieczne zapisanie faktu, że Jane Doe ma rezerwację na pokój w hotelu Seaview Inn na 10–12 kwietnia. Czy to atrybut podmiotu klienta? Czy jest to atrybut podmiotu hotelowego? Czy jest to podmiot rezerwujący z nieruchomościami obejmującymi klienta i hotel? Może to być dowolna lub wszystkie z tych rzeczy w systemie zorientowanym obiektowo. W bazie danych nie jest to żadna z tych rzeczy. To po prostu nagi fakt.

Aby zobaczyć różnicę, rozważ dwa poniższe zapytania. (1) Ile rezerwacji hoteli ma Jane Doe na przyszły rok? (2) Ile pokoi jest zarezerwowanych na 10 kwietnia w Seaview Inn?

W systemie obiektowym zapytanie (1) jest atrybutem jednostki klienta, a zapytanie (2) jest atrybutem jednostki hotelowej. To są obiekty, które ujawniłyby te właściwości w swoich interfejsach API. (Chociaż oczywiście wewnętrzne mechanizmy uzyskiwania tych wartości mogą obejmować odwołania do innych obiektów).

W systemie relacyjnych baz danych oba zapytania sprawdzałyby relację rezerwacji, aby uzyskać ich numery i koncepcyjnie nie ma potrzeby zawracania sobie głowy żadną inną „jednostką”.

Tak więc, próbując przechowywać fakty o świecie - zamiast próbować przechowywać jednostki z atrybutami - konstruowana jest właściwa relacyjna baza danych. Po odpowiednim zaprojektowaniu można z łatwością skonstruować przydatne zapytania, o których nie śniło się w fazie projektowania, ponieważ wszystkie fakty potrzebne do ich spełnienia znajdują się na właściwych miejscach.

Jeffrey L Whitledge
źródło
13
+1 Wreszcie wyspa prawdziwej wiedzy na morzu ignorancji (i odmowy uczenia się czegokolwiek poza ich zasięgiem). Zgoda, to nie jest magia: jeśli RDb jest zaprojektowany przy użyciu zasad RDb, łatwo jest „mapować” lub „projektować” dowolną „klasę”. Zmuszanie RDb do wymagań opartych na klasach jest po prostu niepoprawne.
PerformanceDBA
2
+1, świetna odpowiedź. W każdym razie, czy mógłbyś podać kilka przykładów faktów i dlaczego nie są one bytami? Nadal trudno mi ustalić różnicę między „faktami” a podmiotami. Dziękuję
z
2
Ciekawa odpowiedź. Jak sugerowałbyś modelowanie przykładu Osoba-Pracownik w przyjętej odpowiedzi?
sevenforce
4
@ sevenforce - Projekt DB naprawdę zależy od wymagań systemu, których nie podano. Nie ma wystarczających informacji, aby podjąć decyzję. W wielu przypadkach coś podobnego do projektu „table-per-type” może być właściwe, jeśli nie jest niewolniczo przestrzegane. Na przykład data-rozpoczęcia jest prawdopodobnie dobrą właściwością obiektu Pracownik, ale w bazie danych powinno to być pole w tabeli Zatrudnienie, ponieważ osoba może być zatrudniana wiele razy z wieloma datami rozpoczęcia. Nie ma to znaczenia dla obiektów (które używałyby najnowszych), ale jest ważne w bazie danych.
Jeffrey L Whitledge
6
To prawdziwa perełka odpowiedzi. Potrzeba trochę czasu, aby naprawdę się w to zagłębić i trochę ćwiczeń, aby to naprawić, ale już wpłynęło to na mój proces myślowy o projektowaniu relacyjnej bazy danych.
MarioDS
9

Krótka odpowiedź: nie.

Jeśli chcesz serializować swoje obiekty, użyj ORM lub nawet lepiej czegoś takiego jak activerecord lub prevaylence.

Jeśli potrzebujesz przechowywać dane, przechowuj je w sposób relacyjny (uważaj na to, co przechowujesz i zwracaj uwagę na to, co właśnie powiedział Jeffrey L Whitledge), a nie taki, na który ma wpływ projekt obiektu.

Marcin
źródło
3
+1 Próba modelowania dziedziczenia w bazie danych jest stratą dobrych, relacyjnych zasobów.
Daniel Spiewak
7

Wzorce TPT, TPH i TPC to drogi, którymi się kierujesz, jak wspomniał Brad Wilson. Ale kilka uwag:

  • klasy potomne dziedziczące z klasy bazowej mogą być postrzegane jako słabe jednostki definicji klasy bazowej w bazie danych, co oznacza, że ​​są zależne od swojej klasy bazowej i nie mogą istnieć bez niej. Widziałem wiele razy, że unikalne identyfikatory są przechowywane dla każdej tabeli podrzędnej, zachowując jednocześnie FK do tabeli nadrzędnej. Wystarczy jeden FK, a jeszcze lepiej jest mieć włączoną kaskadę przy usuwaniu dla relacji FK między tabelami podrzędnymi i podstawowymi.

  • W TPT, widząc tylko rekordy tabeli podstawowej, nie jesteś w stanie znaleźć klasy podrzędnej, którą reprezentuje rekord. Jest to czasami potrzebne, gdy chcesz załadować listę wszystkich rekordów (bez wykonywania select na każdej tabeli podrzędnej). Jednym ze sposobów radzenia sobie z tym jest posiadanie jednej kolumny reprezentującej typ klasy potomnej (podobnie do pola rowType w TPH), więc w jakiś sposób mieszanie TPT i TPH.

Powiedzmy, że chcemy zaprojektować bazę danych zawierającą następujący diagram klas kształtów:

public class Shape {
int id;
Color color;
Thickness thickness;
//other fields
}

public class Rectangle : Shape {
Point topLeft;
Point bottomRight;
}

public class Circle : Shape {
Point center;
int radius;
}

Projekt bazy danych dla powyższych klas może wyglądać następująco:

table Shape
-----------
int id; (PK)
int color;
int thichkness;
int rowType; (0 = Rectangle, 1 = Circle, 2 = ...)

table Rectangle
----------
int ShapeID; (FK on delete cascade)
int topLeftX;
int topLeftY;
int bottomRightX;
int bottomRightY;

table Circle
----------
int ShapeID; (FK on delete cascade)  
int centerX;
int center;
int radius;
imang
źródło
4

Istnieją dwa główne typy dziedziczenia, które można skonfigurować w bazie danych, tabela na jednostkę i tabela na hierarchię.

Tabela na jednostkę to podstawowa tabela jednostek, która ma wspólne właściwości wszystkich klas podrzędnych. Następnie dla każdej klasy podrzędnej masz inną tabelę, z której każda ma tylko właściwości mające zastosowanie do tej klasy. Są połączeni 1: 1 przez ich PK

tekst alternatywny

Tabela na hierarchię to miejsce, w którym wszystkie klasy współużytkują tabelę, a właściwości opcjonalne dopuszczają wartość null. Jest to także pole dyskryminatora, które jest liczbą oznaczającą typ aktualnie przechowywanego rekordu

tekst alternatywny SessionTypeID jest dyskryminatorem

Zapytanie o cel na hierarchię jest szybsze, ponieważ nie potrzebujesz złączeń (tylko wartość dyskryminatora), podczas gdy element docelowy na jednostkę musisz wykonać złożone sprzężenia, aby wykryć, jakiego typu jest coś, a także odzyskać wszystkie jego dane.

Edycja: Obrazy, które tu pokazuję, to zrzuty ekranu projektu, nad którym pracuję. Obraz zasobu nie jest kompletny, stąd jego pustka, ale służył głównie do pokazania, jak jest ustawiony, a nie co umieścić w tabelach. To zależy od Ciebie ;). Tabela sesji zawiera informacje o sesji współpracy wirtualnej i może należeć do kilku typów sesji, w zależności od rodzaju współpracy.

mattlant
źródło
Uważam również, że Target na klasę Concrete nie modeluje dobrze dziedziczenia, więc nie pokazałem.
mattlant
Czy możesz dodać odniesienie, skąd pochodzi ilustracja?
chryss
Gdzie są obrazy, o których mówisz na końcu swojej odpowiedzi?
Musa Haidari
1

Normalizowałbyś swoją bazę danych, a to faktycznie odzwierciedlałoby twoje dziedzictwo. Może mieć pogorszenie wydajności, ale tak właśnie jest w przypadku normalizacji. Prawdopodobnie będziesz musiał użyć zdrowego rozsądku, aby znaleźć równowagę.

Per Hornshøj-Schierbeck
źródło
2
dlaczego ludzie uważają, że normalizacja bazy danych obniża wydajność? Czy ludzie myślą również, że zasada DRY obniża wydajność kodu? skąd to błędne przekonanie?
Steven A. Lowe
1
Prawdopodobnie dlatego, że denormalizacja może poprawić wydajność, a zatem normalizacja degraduje ją, mówiąc relatywnie. Nie mogę powiedzieć, że się z tym zgadzam, ale prawdopodobnie tak do tego doszło.
Matthew Scharley
2
Na początku normalizacja może mieć niewielki wpływ na wydajność, ale z biegiem czasu, wraz ze wzrostem liczby wierszy, wydajne połączenia JOIN zaczną przewyższać bardziej pojemne tabele. Oczywiście normalizacja ma inne, większe korzyści - spójność i brak redundancji itp.
Rob
1

powtórz podobną odpowiedź w wątku

w odwzorowaniu OR dziedziczenie mapuje do tabeli nadrzędnej, w której tabele nadrzędne i podrzędne używają tego samego identyfikatora

na przykład

create table Object (
    Id int NOT NULL --primary key, auto-increment
    Name varchar(32)
)
create table SubObject (
    Id int NOT NULL  --primary key and also foreign key to Object
    Description varchar(32)
)

SubObject ma relację klucza obcego z Object. kiedy tworzysz wiersz SubObject, musisz najpierw utworzyć wiersz Object i użyć Id w obu wierszach

EDYCJA: jeśli chcesz również modelować zachowanie, potrzebujesz tabeli typów, która zawiera listę relacji dziedziczenia między tabelami oraz określa nazwę zestawu i klasy, która zaimplementowała zachowanie każdej tabeli

wydaje się przesadą, ale wszystko zależy od tego, do czego chcesz go użyć!

Steven A. Lowe
źródło
Ta dyskusja zakończyła się dodaniem kilku kolumn do każdej tabeli, a nie modelowaniem dziedziczenia. Uważam, że tytuł tej dyskusji powinien zostać zmieniony, aby lepiej odzwierciedlał charakter pytania i dyskusji.
Nawet Mien
1

Używając SQL ALchemy (Python ORM), możesz wykonać dwa typy dziedziczenia.

Jedyne, które miałem doświadczenie, to używanie pojedynczego stołu i dyskryminująca kolumna. Na przykład baza danych owiec (bez żartu!) Przechowała wszystkie owce w jednej tabeli, a tryki i owce były obsługiwane za pomocą kolumny płci w tej tabeli.

W ten sposób możesz zapytać o wszystkie owce i zdobyć wszystkie owce. Lub możesz zapytać tylko przez Ram, a dostaniesz tylko Barany. Możesz także na przykład nawiązać relację, która może być tylko Baranem (tj. Ojcem Owcy) i tak dalej.

Matthew Schinckel
źródło
1

Zauważ, że niektóre silniki baz danych już zapewniają mechanizmy dziedziczenia, takie jak Postgres . Spójrz na dokumentację .

Na przykład, możesz zapytać system Osoba / Pracownik opisany w odpowiedzi powyżej w następujący sposób:

  / * To pokazuje imiona wszystkich osób lub pracowników * /
  WYBIERZ imię z osoby; 

  / * Pokazuje tylko datę rozpoczęcia dla wszystkich pracowników * /
  SELECT startdate FROM Employee;

To wybór Twojej bazy danych, nie musisz być szczególnie sprytny!

Pierre
źródło