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).
.net
sql-server
oop
inheritance
database-design
Nawet Mien
źródło
źródło
Odpowiedzi:
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:
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
źródło
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.
źródło
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.
źródło
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:
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;
źródło
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
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
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.
źródło
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ę.
źródło
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ć!
źródło
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.
źródło
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 wybór Twojej bazy danych, nie musisz być szczególnie sprytny!
źródło