Anche se è possibile inserire la sequenza di identità su più tabelle, la tabella dei commenti non sarà in grado di fare riferimento a entrambe le colonne in una singola chiave esterna.
Il modo migliore per eseguire questa operazione, in termini di teoria del progetto di database relazionale, consiste nel creare due tabelle di commenti. Ma ovviamente, vuoi evitarlo, probabilmente per ragioni di riutilizzo del codice.
L'approccio pragmatico più diretto sarebbe quello di inserire due colonne di chiavi esterne nella tabella dei commenti e creare un valore null e l'altro non null per ogni commento.
Un altro approccio, che potrebbe essere il miglior compromesso, è questo. Fai riferimento nella tua domanda a un "ID entità". Quindi crea una tabella Entity! Quindi gli autori, i libri e i commenti possono tutti fare riferimento a nella tabella.
A cura di aggiungere:
Philip Kelley, Ray, e (credo) Artic hanno tutti proposto di modificare la tabella di commento aggiungendo un entity_id
, che può riferirsi sia alla book_id
o author_id
, ed un bandiera di qualche tipo (char(1)
, tinyint
, e boolean
, rispettivamente) che indica a quale di questi viene fatto riferimento.
Questa non è una buona soluzione per molte ragioni, sia pragmatiche (tra cui integrità dei dati, reporting, efficienza) e teorica.
Il primo e più ovvio problema è il problema dell'integrità dei dati. Un sistema di database relazionale dovrebbe sempre essere responsabile del mantenimento dell'integrità dei propri dati, e ci sono modi naturali e preferiti che il DB è progettato per fare questo. Uno dei più importanti di questi meccanismi è il sistema di chiavi estranee. Se la colonna comment.entity_id
deve fare riferimento sia a book.book_id
sia a author.author_id
, non è possibile creare una chiave esterna per questa colonna.
Certo, è possibile inserire un controllo nelle procedure memorizzate DML (inserire, aggiornare, eliminare) per verificare i riferimenti, ma questo si trasformerebbe rapidamente in un grande caos, poiché tutte le operazioni DML su tutte e tre le tabelle sarebbero coinvolte.
E questo ci porta al problema dell'efficienza. Ogni volta che viene eseguita una query sulla tabella comment
, saranno necessari i join alla tabella author
o book
o entrambi. Il sistema di generazione del piano di query non avrà chiavi esterne disponibili per l'ottimizzazione, pertanto le sue prestazioni potrebbero essere ridotte.
Quindi ci sono problemi con questo schema nei rapporti. Qualsiasi sistema di generazione di report avrà problemi con questo tipo di sistema. Certo, questo non sarà un problema per i programmatori esperti, ma qualsiasi rapporto ad hoc dell'utente dovrà prendere in giro la logica dietro quando lo event_id
significa questo o quello, e potrebbe essere un pessimo affare. Forse non userai mai strumenti di generazione di rapporti su questo database. Ma poi di nuovo, nessuno sa dove verrà in definitiva utilizzato un database. Perché non lavorare con il sistema per consentire qualcosa?
E questo ci porta ai problemi teorici.
Nella teoria dei database relazionali, ogni riga (a.k.a. "tupla") in ogni tabella ("variabile di relazione") rappresenta una proposizione sul mondo reale. Progettare un tavolo è decidere la forma di quella proposta. Diamo un'occhiata ad alcuni esempi di come questo potrebbe funzionare.
comment (comment_id int, comment_type char(1), entity_id int,
user_id int, comment_text nvarchar(max), comment_date datetime)
/* comment_id identifies a comment (comment_text) that a user (user_id)
has made about a book (entity_id if comment_type = 'B') or author
(entity_id if comment_type = 'A') at a particular date and
time (comment_date).*/
questo caso è chiaro che la colonna (o "attributo") chiamati entity_id
sta facendo doppio lavoro. In realtà non rappresenta nulla, tranne che con riferimento a un'altra colonna. Questo è fattibile, ma insoddisfacente.
comment (comment_id int, book_id int, author_id int, user_id int,
comment_text nvarchar(max), comment_date datetime)
/* comment_id identifies a comment (comment_text) that a user (user_id)
has made about a book (book_id if not null) or author (author_id if
not null) at a particular date and time (comment_date). */
Questo ci compra le chiavi esterne che sono la più grande omissione della prima versione. Ma questo non è ancora molto soddisfacente, a meno che un singolo commento possa riferirsi sia a un libro sia a un autore (che potrebbe essere ragionevole). Le colonne cancellabili sono un segnale di avvertimento che qualcosa non va nel design, e potrebbe anche essere il caso. Un vincolo di controllo può essere necessario per evitare un commento che non faccia riferimento a nulla o sia a un libro che a un autore, se ciò non è consentito.
Dal punto di vista teorico (e, quindi, il mio punto di vista :)) v'è una chiara opzione migliore:
book_comment (book_comment_id int, book_id int, user_id int,
comment_text nvarchar(max), comment_date datetime)
/* book_comment_id identifies a comment (comment_text) that a
user (user_id) has made about a book (book_id) at a particular
date and time (comment_date). */
author_comment (author_comment_id int, author_id int, user_id int,
comment_text nvarchar(max), comment_date datetime)
/* author_comment_id identifies a comment (comment_text) that a
user (user_id) has made about an author (author_id) at a particular
date and time (comment_date). */
Questa ultima opzione avrebbe fornito la migliore efficienza, integrità dei dati e la facilità di reporting. E l'unica spesa sarebbe che le procedure memorizzate DML avrebbero bisogno di mettere i commenti nelle tabelle giuste, il che non è un grosso problema, dal momento che dovevano sapere comunque a cosa si riferivano i commenti.
Se il tuo piano era quello di recuperare tutti i commenti per un libro o un autore contemporaneamente, puoi facilmente creare una vista in cima a queste tabelle che riproduce gli altri disegni, se è quello che vuoi fare.
create view comments as
select
book_comment_id as comment_id,
book_id as entity_id,
comment_text,
'B' as comment_type
from book_comment
union
select
author_comment_id as comment_id,
author_id as entity_id,
comment_text,
'A' as comment_type
from author_comment
Non è possibile avere un singolo punto di chiave esterna su due tabelle diverse, anche con una bandiera a portata di mano. –
Il tuo suggerimento ("Probabilmente aggiungerei una colonna come" CommentorType "ai commenti") è il percorso che avevo prima di decidere di pubblicare e assicurarmi che non ci fosse un modo più semplice per farlo. Grazie. – johnnycakes
Ma aggiungere il tipo di commentatore ai commenti non è una buona soluzione! Non farlo! Te ne pentirai! OK, mi sento meglio ora. Andare avanti. –