Sqlserver
 sql >> Database >  >> RDS >> Sqlserver

Come creare più uno a uno

Stai usando l'ereditarietà (nota anche nella modellazione entità-relazione come "sottoclasse" o "categoria"). In generale, ci sono 3 modi per rappresentarlo nel database:

  1. "Tutte le classi in una tabella": Avere solo una tabella che "copra" le classi padre e tutte le classi figlio (cioè con tutte le colonne padre e figlio), con un vincolo CHECK per garantire che il sottoinsieme di campi corretto sia non NULL (cioè due figli diversi non "mischiano").
  2. "Classe concreta per tavolo": Avere una tabella diversa per ogni figlio, ma nessuna tabella padre. Ciò richiede che le relazioni dei genitori (nel tuo caso Inventario <- Archiviazione) vengano ripetute in tutti i figli.
  3. "Classe per tavolo": Avere una tabella padre e una tabella separata per ogni figlio, che è quello che stai cercando di fare. Questo è il più pulito, ma può costare un po' di prestazioni (soprattutto quando si modificano i dati, non tanto quando si eseguono query perché puoi unirti direttamente dal figlio e saltare il genitore).

Di solito preferisco il 3° approccio, ma impongo entrambi la presenza e l'esclusività di un bambino a livello di applicazione. L'applicazione di entrambi a livello di database è un po' macchinosa, ma può essere eseguita se il DBMS supporta i vincoli posticipati. Ad esempio:

CHECK (
    (
        (VAN_ID IS NOT NULL AND VAN_ID = STORAGE_ID)
        AND WAREHOUSE_ID IS NULL
    )
    OR (
        VAN_ID IS NULL
        AND (WAREHOUSE_ID IS NOT NULL AND WAREHOUSE_ID = STORAGE_ID)
    )
)

Ciò imporrà sia l'esclusività (a causa del CHECK ) e la presenza (dovuta alla combinazione di CHECK e FK1 /FK2 ) del bambino.

Sfortunatamente, MS SQL Server non supporta i vincoli posticipati, ma potresti essere in grado di "nascondere" l'intera operazione dietro le procedure memorizzate e impedire ai client di modificare direttamente le tabelle.

Solo l'esclusività può essere applicata senza vincoli differiti:

Il STORAGE_TYPE è un discriminatore di tipo, solitamente un numero intero per risparmiare spazio (nell'esempio sopra, 0 e 1 sono "conosciuti" dall'applicazione e interpretati di conseguenza).

Il VAN.STORAGE_TYPE e WAREHOUSE.STORAGE_TYPE possono essere calcolate colonne (ovvero "calcolate") per risparmiare spazio di archiviazione ed evitare la necessità del CHECK s.

--- MODIFICA ---

Le colonne calcolate funzionerebbero in SQL Server in questo modo:

CREATE TABLE STORAGE (
    STORAGE_ID int PRIMARY KEY,
    STORAGE_TYPE tinyint NOT NULL,
    UNIQUE (STORAGE_ID, STORAGE_TYPE)
);

CREATE TABLE VAN (
    STORAGE_ID int PRIMARY KEY,
    STORAGE_TYPE AS CAST(0 as tinyint) PERSISTED,
    FOREIGN KEY (STORAGE_ID, STORAGE_TYPE) REFERENCES STORAGE(STORAGE_ID, STORAGE_TYPE)
);

CREATE TABLE WAREHOUSE (
    STORAGE_ID int PRIMARY KEY,
    STORAGE_TYPE AS CAST(1 as tinyint) PERSISTED,
    FOREIGN KEY (STORAGE_ID, STORAGE_TYPE) REFERENCES STORAGE(STORAGE_ID, STORAGE_TYPE)
);

-- We can make a new van.
INSERT INTO STORAGE VALUES (100, 0);
INSERT INTO VAN VALUES (100);

-- But we cannot make it a warehouse too.
INSERT INTO WAREHOUSE VALUES (100);
-- Msg 547, Level 16, State 0, Line 24
-- The INSERT statement conflicted with the FOREIGN KEY constraint "FK__WAREHOUSE__695C9DA1". The conflict occurred in database "master", table "dbo.STORAGE".

Sfortunatamente, SQL Server richiede una colonna calcolata che viene utilizzata in un campo esterno chiave da PERSistere. Altri database potrebbero non avere questa limitazione (ad es. le colonne virtuali di Oracle), il che può far risparmiare spazio di archiviazione.