2010-03-11 6 views
5

Posso avere una colonna "identità" (unica, non ripetibile) su più tabelle? Ad esempio, supponiamo di avere due tabelle: libri e autori.Posso creare un campo identità su più tabelle in SQL Server?

Authors 
    AuthorID 
    AuthorName 
Books 
    BookID 
    BookTitle 

La colonna BookID e la colonna AuthorID sono colonne di identità. Voglio che la parte identità si estenda su entrambe le colonne. Quindi, se c'è un AuthorID con un valore di 123, quindi non può esserci un BookID con un valore di 123. E viceversa.

Spero che abbia senso.

È possibile?

Grazie.

Perché voglio farlo? Sto scrivendo un'app MVC APS.NET. Sto creando una sezione di commenti. Gli autori possono avere commenti. I libri possono avere commenti. Voglio essere in grado di passare un ID entità (un ID libro o un ID autore) a un'azione e fare in modo che l'azione tiri su tutti i commenti corrispondenti. All'azione non interessa se si tratta di un libro o di un autore o altro. Sembra ragionevole?

risposta

2

La risposta breve è: No, non è possibile farlo (almeno in MS SQL Server fino al 2008).

È possibile creare una nuova tabella, "CommentableEntity", collegare la colonna Identity lì, quindi definire le chiavi esterne in Autori e Libri per fare riferimento a essa come tabella padre, quindi eseguire uno dei numerosi trucchi per garantire che un dato valore ID non è assegnato a entrambe le tabelle ...ma questa è una pessima idea, perché il modello di dati che hai costruito implicherebbe che Autori e Libri siano tipi di dati correlati, e in realtà non lo sono.

Si potrebbe avere una tabella separata, Commenti, inserire la colonna Identità e parcheggiare una colonna Commento su Autori e Libri. Tuttavia, ciò limiterebbe ogni libro e autore a un solo commento.

Io, probabilmente aggiungerei una colonna come "CommentorType" alla tabella dei commenti e mettere una bandiera lì indicando la fonte del commento ("A" per l'autore, "B" per il libro). Costruisci una chiave primaria su "CommentorId + CommentorType", e dovrebbe funzionare abbastanza bene e sarebbe banale aggiungere altri tipi di commentatori man mano che il sistema si espande.

+0

Non è possibile avere un singolo punto di chiave esterna su due tabelle diverse, anche con una bandiera a portata di mano. –

+0

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

+0

Ma aggiungere il tipo di commentatore ai commenti non è una buona soluzione! Non farlo! Te ne pentirai! OK, mi sento meglio ora. Andare avanti. –

0

Come suggerimento: provare a utilizzare la tabella come ComentId, EntityId, isBook, Comment per commenti. isBook è di tipo booleano e non c'è molto da trovare. Il tuo concetto non è buono dal punto di vista relazionale.

5

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 
+0

Hi Jeffrey, Perché pensi che sia una cattiva idea utilizzare un "ID tipo commentatore" insieme a "ID entità"? Perché i tuoi ultimi due suggerimenti sono migliori? Sto ancora imparando! Grazie. – johnnycakes

+0

OK. Ho indirizzato la tua domanda in una modifica alla risposta. –

+0

Questo, IMO, è la risposta corretta. L'utilizzo di una struttura EAV per questo tipo di soluzione è la risposta sbagliata e diventerà brutto nei rapporti. L'aggiunta di un'altra tabella non costa molto ma offre molti vantaggi, tra cui la possibilità per i commenti degli autori di avere attributi che i commenti dei libri non hanno. – Thomas

0

Il server SQL non supporta questo. Potresti farcela da sola con una tabella di identificazione, ma sarebbe più un lavoro che ne vale la pena.

suggerisco vostro tavolo commento simile a questa:

comment_id int identity 
comment_type tinyint 
entity_id int 

comment_type specifica se il commento appartiene a un libro, un autore, o qualcos'altro si aggiunge in futuro. entity_id è l'id del libro, autore, qualunque cosa. In questo schema, non importa se gli id ​​dei libri o degli autori si sovrappongono.

O, se è possibile passare a Oracle, utilizzare una sequenza :)

1

In realtà, Joe Celko suggerisce il this blog di utilizzare una sequenza personalizzata nel database, e poi, per qualsiasi chiave primaria delle tabelle desiderati , specifica i loro valori predefiniti per ottenere il numero successivo dalla sequenza personalizzata.

Ecco un esempio di codice dal suo blog:

CREATE SEQUENCE Service_Ticket_Seq 
AS INTEGER 
START WITH 1 
INCREMENT BY 1 
MINVALUE 1 
MAXVALUE 100 
CYCLE; 

CREATE TABLE Meats 
(ticket_seq INTEGER DEFAULT NEXT VALUE FOR Service_Ticket_Seq 
     PRIMARY KEY, 
meat_type VARCHAR(15) NOT NULL); 

CREATE TABLE Fish 
(ticket_seq INTEGER DEFAULT NEXT VALUE FOR Service_Ticket_Seq 
     PRIMARY KEY, 
fish_type VARCHAR(15) NOT NULL); 

INSERT INTO Meats (meat_type) VALUES ('pig'); 
INSERT INTO Fish (fish_type) VALUES ('squid'); 

select * from Meats 

select * from Fish 

Detto questo, un campo di identità si estende su più tavoli è possibile in MS SQL.

+0

Sì, funziona in Microsoft SQL Server 2014 –

Problemi correlati