È possibile vincolare una tabella per avere un valore in una sola di una serie di colonne
-
05-07-2019 - |
Domanda
Ho una tabella che deve collegare una delle tre tabelle separate, ma dovrebbe collegarsi solo a una di esse, ad esempio
Main_Table
id UNIQUEIDENTIFIER
t1_id UNIQUEIDENTIFIER
t2_id INT
t3_id INT
T1
id UNIQUEIDENTIFIER
name VARCHAR(255)
T2
id INT
name VARCHAR(255)
T3
id INT
name VARCHAR(255)
È possibile avere un vincolo per cui solo uno di t1, t2 o t3 non è nullo per volta?
Questo è solo un cattivo design? In tal caso, quali suggerimenti daresti per il design?
Modifica
Mi è stato chiesto di elaborare le ragioni alla base di questo particolare design.
Main_Table sta tentando di essere una tabella dei pagatori, che potrebbe fare riferimento a un singolo utente (T1), un gruppo di singoli utenti (T2) o un gruppo di gruppi (T3).
Questo è un progetto di database che ho ereditato e purtroppo non è soggetto a modifiche.
Il mio problema più grande è che devo associare tra tipi diversi, quindi un campo di tipo non funzionerà qui poiché gli indici sono diversi.
Soluzione
Il disegno che stai descrivendo si chiama archi esclusivi . Sì, è un design piuttosto fragile e fallisce persino alcune regole di normalizzazione.
Ecco un'alternativa:
Main_Table
id UNIQUEIDENTIFIER
t_id INT NOT NULL
FOREIGN KEY (t_id) REFERENCES T0 (id)
T0
id UNIQUEIDENTIFIER
type INT NOT NULL CHECK (type IN (1,2,3))
UNIQUE KEY (id, type)
T1
id INT
type INT NOT NULL CHECK (type = 1)
name VARCHAR(255)
FOREIGN KEY (id, type) REFERENCES T0 (id, type)
T2
id INT
type INT NOT NULL CHECK (type = 2)
name VARCHAR(255)
FOREIGN KEY (id, type) REFERENCES T0 (id, type)
T3
id INT
type INT NOT NULL CHECK (type = 3)
name VARCHAR(255)
FOREIGN KEY (id, type) REFERENCES T0 (id, type)
Con questo disegno, ogni riga in Main_Table
deve fare riferimento a una riga in T0
.
Allo stesso modo, ogni riga in T0
può essere padre di una sola riga in T1
, T2
o T3
.
Questo è un modo per implementare l'ereditarietà delle tabelle di classe e le associazioni polimorfiche senza interrompere l'integrità referenziale.
Main_Table sta tentando di essere un pagatore tabella, che potrebbe fare riferimento a un singolo utente (T1), un gruppo di singoli utenti (T2) o un gruppo di gruppi (T3).
Bene, quindi pensaci in termini di design orientato agli oggetti. Se avessi tre classi che potrebbero fungere da destinatari dei pagamenti, creeresti una interfaccia chiamata Payable
o qualcosa del genere, in modo che ognuna possa contare sulla digitazione di quegli oggetti. Tutti gli oggetti Payable
devono avere un metodo sendPayment ()
per esempio. In alcuni linguaggi OO, l'interfaccia è una superclasse ed è chiamata classe astratta o pura classe virtuale .
La tabella T0
funziona come un tipo comune per ciascuna delle tabelle figlio T1
, T2
e T3
. Quando Main_Table
ha una chiave esterna per T0
, è come dire Main_Table
deve avere un riferimento a qualche entità che è Pagabile
, ma qualsiasi oggetto che discende da quella superclasse è accettabile da usare.
La colonna type
è solo un trucco per assicurarsi che un dato T0.id
possa essere referenziato solo da una tabella di sottoclassi alla volta. È un po 'facoltativo, se puoi fare affidamento sulla logica dell'applicazione per inserire una determinata riga figlio in una sola delle tabelle delle sottoclassi.
Vedi anche la sezione Associazioni polimorfiche nella mia presentazione " SQL Antipatterns Strike Back . "
Altri suggerimenti
Se il tuo DB ha vincoli di controllo, potresti generare un brutto kludge come:
ALTER TABLE Main_Table
add constraint CK_ThisWorksButItsUgly
check ( ( case when t1_id is null then 0 else 1 end
+ case when t2_id is null then 0 else 1 end
+ case when t3_id is null then 0 else 1 end) = 1)
Alcune sintassi potrebbero essere errate lì, ma hai capito. Probabilmente avrebbe funzionato abbastanza bene - il controllo avrebbe dovuto sparare solo quando una delle colonne fosse stata modificata - ma in nessun modo è carina.
Gli archi esclusivi di Bill Karwin sono fantastici, se puoi riprogettare il design del database.
oltre al cattivo design, se non puoi cambiarlo, è possibile usare un trigger per applicare questo vincolo
Questa è l'ennesima istanza del modello gen-spec.
Vai a articoli web su " specializzazione generalizzazione modellistica relazionale " ;. Ce ne sono alcuni eccellenti là fuori.