2013-10-24 10 views
7

Ho eseguito di recente questo scenario più volte in diversi progetti. Ecco un diagramma di quattro tabelle, etichettati con le lettere:Progettazione di relazioni a quattro tabelle

 A 
    1/\ 1 
/ \ 
*/ \ * 
B  C 
1 \ /1 
    \ /
    * \/* 
    D 

In questo scenario, è possibile che i dati di diventare incoerente se le chiavi da B a A e C a A non corrispondono per un dato D.

Per una specifica (fatta) ad esempio, immaginare A è Company, B è Employee, C è Project, e D è WorkItem. In questo caso, non c'è nulla che impedisca la creazione di un oggetto di lavoro che afferma di essere assegnato a una persona che non lavora nemmeno per la società proprietaria del progetto.

Sono principalmente curioso, c'è una soluzione a questo problema? So che nelle applicazioni reali, dove questo è importante, puoi usare i trigger o altre protezioni. Non ho trovato un modo per cambiare le tabelle per rendere impossibile tale incoerenza. C'è un modo?

Si noti che solo recidere una delle connessioni, come C-A non funziona, perché se non esistono D 's per quella C si avrebbe modo di tracciare le connessioni di nuovo a A.

+0

Buona domanda. Ho sempre messo delle protezioni per prevenire questo tipo di cose nel livello dell'applicazione. Sarà interessante vedere cosa hanno da dire gli altri. –

+0

Io uso per applicare questo tipo di restrizione nelle regole aziendali, non come trigger. –

+0

Ridefinisci le tue entità: dipendente - >> persona (ricorda: un ex-emplyee è stato un dipendente una volta) Può essere necessario aggiungere un asse temporale (e alcune altre dimensioni extra; cercare le dimensioni nascoste). – wildplasser

risposta

7

Utilizzare le chiavi composite (vale a dire una chiave comprendente più campi) per le tabelle downstream. Poi in D, è possibile utilizzare un solo campo per stiva di una chiave:

[EDIT: Risolto stupido copia & errore incolla nel D's 2 ° FK]

CREATE TABLE A (
    A_ID INTEGER PRIMARY KEY 
    -- Any other fields you want... 
); 

CREATE TABLE B (
    A_ID INTEGER REFERENCES A.A_ID, 
    B_ID INTEGER, 
    -- Any other fields you want... 
    PRIMARY KEY (A_ID, B_ID) 
); 

CREATE TABLE C (
    A_ID INTEGER REFERENCES A.A_ID, 
    C_ID INTEGER, 
    -- Any other fields you want... 
    PRIMARY KEY (A_ID, C_ID) 
); 

CREATE TABLE D (
    A_ID INTEGER, -- This field forms part of the FK for BOTH B and C 
    B_ID INTEGER, 
    C_ID INTEGER, 
    D_ID INTEGER, 
    -- Any other fields you want... 
    PRIMARY KEY (A_ID, B_ID, C_ID, D_ID), 
    FOREIGN KEY (A_ID, B_ID) REFERENCES B (A_ID, B_ID), 
    FOREIGN KEY (A_ID, C_ID) REFERENCES C (A_ID, C_ID) 
); 

ho non testato quanto sopra SQL, ma si spera l'idea. Si noti che D non ha bisogno di un terzo vincolo FK su A, perché questo è già implicito dagli altri FK (in realtà è implicito separatamente da ciascuno di essi).

referenziale controllo di integrità è sempre meglio di trigger - almeno con PostgreSQL è, e ho il sospetto che sia vero con tutti i RDBMS.

+0

Credo che questo sia l'unico modo, ma è un inferno per lo sviluppo (altre entità potrebbero richiedere anche chiavi più lunghe). Anche le prestazioni saranno terribili. –

+1

@LluisMartinez: mi aspetto è più veloce da usare singolo intero (o forse GUID) PK, ma non ho avuto problemi di prestazioni con le tabelle organizzate in questo modo su PostgreSQL, alcune delle quali hanno milioni di righe. In realtà trovo che rende il debug molto più semplice - quando si lavora con un record da una tabella dipendente, il PK ti dice subito molto su di esso, invece di essere solo un token opaco. In secondo luogo, puoi spesso ordinare o raggruppare in base a questi campi FK senza bisogno di unirsi alle tabelle padre. –

Problemi correlati