Quali sono le migliori pratiche per modellare l'ereditarietà nei database?
Quali sono i compromessi (ad esempio queriability)?
(Sono più interessato a SQL Server e .NET, ma voglio anche capire come altre piattaforme affrontano questo problema.)
Quali sono le migliori pratiche per modellare l'ereditarietà nei database?
Quali sono i compromessi (ad esempio queriability)?
(Sono più interessato a SQL Server e .NET, ma voglio anche capire come altre piattaforme affrontano questo problema.)
Risposte:
Esistono diversi modi per modellare l'ereditarietà in un database. Quale scegli dipende dalle tue esigenze. Ecco alcune opzioni:
Tabella per tipo (TPT)
Ogni classe ha il suo tavolo. La classe base contiene tutti gli elementi della classe base e ogni classe che ne deriva ha una propria tabella, con una chiave primaria che è anche una chiave esterna della tabella della classe base; la classe della tabella derivata contiene solo i diversi elementi.
Quindi per esempio:
class Person {
public int ID;
public string FirstName;
public string LastName;
}
class Employee : Person {
public DateTime StartDate;
}
Si tradurrebbe in tabelle come:
table Person
------------
int id (PK)
string firstname
string lastname
table Employee
--------------
int id (PK, FK)
datetime startdate
Tabella per gerarchia (TPH)
C'è una singola tabella che rappresenta tutta la gerarchia dell'ereditarietà, il che significa che molte delle colonne saranno probabilmente sparse. Viene aggiunta una colonna discriminatore che indica al sistema che tipo di riga è.
Date le classi sopra, si finisce con questa tabella:
table Person
------------
int id (PK)
int rowtype (0 = "Person", 1 = "Employee")
string firstname
string lastname
datetime startdate
Per tutte le righe di tipo 0 (Persona), la data di inizio sarà sempre nulla.
Tavolo per calcestruzzo (TPC)
Ogni classe ha una propria tabella completamente formata senza riferimenti ad altre tabelle.
Date le classi sopra, si finisce con queste tabelle:
table Person
------------
int id (PK)
string firstname
string lastname
table Employee
--------------
int id (PK)
string firstname
string lastname
datetime startdate
La corretta progettazione del database non è come la corretta progettazione degli oggetti.
Se stai pianificando di utilizzare il database per qualcosa di diverso dalla semplice serializzazione dei tuoi oggetti (come report, query, uso multi-applicazione, business intelligence, ecc.) Allora non consiglio alcun tipo di semplice mappatura dagli oggetti alle tabelle.
Molte persone pensano a una riga in una tabella di database come un'entità (ho passato molti anni a pensare in quei termini), ma una riga non è un'entità. È una proposta Una relazione di database (ovvero tabella) rappresenta una dichiarazione di fatto sul mondo. La presenza della riga indica che il fatto è vero (e viceversa, la sua assenza indica che il fatto è falso).
Con questa comprensione, puoi vedere che un singolo tipo in un programma orientato agli oggetti può essere memorizzato in una dozzina di relazioni diverse. E una varietà di tipi (uniti per eredità, associazione, aggregazione o completamente non affiliati) possono essere parzialmente archiviati in una singola relazione.
È meglio chiedersi, quali fatti vuoi archiviare, a quali domande vuoi rispondere, quali rapporti vuoi generare.
Una volta creato il progetto DB corretto, è semplice creare query / viste che consentano di serializzare i propri oggetti su tali relazioni.
Esempio:
In un sistema di prenotazione alberghiera, potrebbe essere necessario memorizzare il fatto che Jane Doe ha una prenotazione per una camera al Seaview Inn per il 10-12 aprile. È un attributo dell'entità cliente? È un attributo dell'entità hotel? È un'entità di prenotazione con proprietà che includono clienti e hotel? Potrebbe essere una o tutte queste cose in un sistema orientato agli oggetti. In un database, non è nessuna di queste cose. È semplicemente un dato di fatto.
Per vedere la differenza, considera le seguenti due query. (1) Quante prenotazioni alberghiere ha Jane Doe per il prossimo anno? (2) Quante camere sono prenotate per il 10 aprile al Seaview Inn?
In un sistema orientato agli oggetti, query (1) è un attributo dell'entità cliente e query (2) è un attributo dell'entità hotel. Quelli sono gli oggetti che espongono quelle proprietà nelle loro API. (Anche se, ovviamente, i meccanismi interni con cui vengono ottenuti quei valori possono comportare riferimenti ad altri oggetti.)
In un sistema di database relazionale, entrambe le query esaminerebbero la relazione di prenotazione per ottenere i loro numeri e concettualmente non è necessario preoccuparsi di qualsiasi altra "entità".
Pertanto, è tentando di archiviare fatti sul mondo, piuttosto che tentare di archiviare entità con attributi, che viene costruito un database relazionale adeguato. E una volta che è stato progettato in modo appropriato, è possibile costruire facilmente query utili che non sono state ancora sognate durante la fase di progettazione, poiché tutti i fatti necessari per soddisfare tali domande si trovano al loro posto.
Employment
tabella, che raccoglie tutti gli impieghi con le loro date di inizio. Quindi, se conoscere l'attuale data di inizio del lavoro di un Employer
è importante, questo potrebbe essere un caso d'uso corretto per un View
, che include quella proprietà eseguendo una query? (nota: sembra a causa del '-' subito dopo il mio nick non ho ricevuto alcuna notifica sul tuo commento)
Risposta breve: non lo fai.
Se hai bisogno di serializzare i tuoi oggetti, usa un ORM, o ancora meglio qualcosa come activerecord o prevaylence.
Se è necessario archiviare i dati, archiviarli in modo relazionale (facendo attenzione a ciò che si sta archiviando e prestando attenzione a ciò che Jeffrey L Whitledge ha appena detto), non uno influenzato dalla progettazione degli oggetti.
I modelli TPT, TPH e TPC sono i modi in cui vai, come menzionato da Brad Wilson. Ma un paio di note:
le classi figlio che ereditano da una classe base possono essere viste come entità deboli nella definizione della classe base nel database, nel senso che sono dipendenti dalla loro classe base e non possono esistere senza di essa. Ho visto molte volte che ID univoci sono archiviati per ogni tabella figlio mantenendo l'FK nella tabella padre. Un FK è appena sufficiente ed è ancora meglio avere l'abilitazione in cascata on-delete per la relazione FK tra le tabelle figlio e base.
In TPT, vedendo solo i record della tabella di base, non è possibile trovare quale classe figlio rappresenta il record. Questo a volte è necessario, quando si desidera caricare un elenco di tutti i record (senza farlo select
su ogni tabella figlio). Un modo per gestirlo è quello di avere una colonna che rappresenta il tipo della classe figlio (simile al campo rowType nel TPH), mescolando così il TPT e il TPH in qualche modo.
Supponiamo di voler progettare un database che contenga il seguente diagramma della classe di forma:
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;
}
La progettazione del database per le classi precedenti può essere così:
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;
Esistono due tipi principali di ereditarietà che è possibile impostare in un DB, tabella per entità e tabella per Gerarchia.
Tabella per entità è dove si dispone di una tabella di entità di base che ha proprietà condivise di tutte le classi figlio. Quindi, per classe figlio si dispone di un'altra tabella ciascuna con solo le proprietà applicabili a quella classe. Sono collegati 1: 1 dai loro PK
Tabella per gerarchia è dove tutte le classi hanno condiviso una tabella e le proprietà opzionali sono nullable. Il loro è anche un campo discriminatore che è un numero che indica il tipo attualmente detenuto dal record
SessionTypeID è discriminatore
Il target per gerarchia è più veloce da interrogare poiché non è necessario unire (solo il valore del discriminatore), mentre il target per entità è necessario eseguire join complessi per rilevare di che tipo è qualcosa e recuperare tutti i suoi dati.
Modifica: le immagini che mostro qui sono schermate di un progetto a cui sto lavorando. L'immagine dell'asset non è completa, quindi la sua svuotamento, ma era principalmente per mostrare come la sua configurazione, non cosa mettere nei tuoi tavoli. Questo dipende da te ;). La tabella delle sessioni contiene informazioni sulla sessione di collaborazione virtuale e può essere di diversi tipi di sessioni a seconda del tipo di collaborazione coinvolto.
Dovresti normalizzare il tuo database e questo rispecchierebbe effettivamente la tua eredità. Potrebbe avere un peggioramento delle prestazioni, ma è così con la normalizzazione. Probabilmente dovrai usare il buon senso per trovare l'equilibrio.
ripetizione di una risposta di thread simile
nel mapping OR, l'ereditarietà viene mappata su una tabella principale in cui le tabelle padre e figlio utilizzano lo stesso identificatore
per esempio
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 ha una relazione di chiave esterna con Object. quando si crea una riga Oggetto secondario, è necessario innanzitutto creare una riga Oggetto e utilizzare l'ID in entrambe le righe
EDIT: se stai cercando anche di modellare il comportamento, avresti bisogno di una tabella Type che elenca le relazioni di ereditarietà tra le tabelle e specifichi l'assembly e il nome della classe che ha implementato il comportamento di ogni tabella
sembra eccessivo, ma tutto dipende da cosa vuoi usarlo!
Utilizzando SQL ALchemy (Python ORM), è possibile eseguire due tipi di ereditarietà.
Quello che ho avuto esperienza è usare una tabella di singoli e avere una colonna discriminante. Ad esempio, un database di pecore (niente scherzo!) Memorizzava tutte le pecore in una tabella e Rams ed Ewes venivano gestiti usando una colonna di genere in quella tabella.
Pertanto, è possibile eseguire una query per tutte le pecore e ottenere tutte le pecore. Oppure puoi eseguire una query solo per Ram e otterrà solo Rams. Puoi anche fare cose come avere una relazione che può essere solo un ariete (cioè il padre di una pecora) e così via.
Si noti che alcuni motori di database forniscono già meccanismi di ereditarietà nativamente come Postgres . Guarda la documentazione .
Ad esempio, si dovrebbe interrogare il sistema Persona / Dipendente descritto in una risposta sopra come questo:
/ * Questo mostra il nome di tutte le persone o dipendenti * / SELEZIONA nome DA persona; / * Mostra la data di inizio di tutti i dipendenti * / SELEZIONA data di inizio DA Dipendente;
In questo è la scelta del tuo database, non devi essere particolarmente intelligente!