Sqlserver
 sql >> Base de données >  >> RDS >> Sqlserver

Comment créer plusieurs one to one

Vous utilisez l'héritage (également connu dans la modélisation entité-relation sous le nom de "sous-classe" ou "catégorie"). En général, il existe 3 façons de le représenter dans la base de données :

  1. "Toutes les classes dans un seul tableau" : Avoir une seule table "couvrant" le parent et toutes les classes enfant (c'est-à-dire avec toutes les colonnes parent et enfant), avec une contrainte CHECK pour s'assurer que le bon sous-ensemble de champs n'est pas NULL (c'est-à-dire que deux enfants différents ne "se mélangent pas").
  2. "Classe concrète par table" : Avoir une table différente pour chaque enfant, mais pas de table parent. Cela nécessite que les relations des parents (dans votre cas Inventory <- Storage) soient répétées dans tous les enfants.
  3. "Classe par table" : Avoir une table parent et une table séparée pour chaque enfant, c'est ce que vous essayez de faire. C'est le plus propre, mais cela peut coûter des performances (principalement lors de la modification des données, pas tellement lors de l'interrogation, car vous pouvez joindre directement à partir de l'enfant et ignorer le parent).

Je préfère généralement la 3ème approche, mais applique à la fois la présence et l'exclusivité d'un enfant au niveau de l'application. L'application des deux au niveau de la base de données est un peu fastidieuse, mais peut être effectuée si le SGBD prend en charge les contraintes différées. Par exemple :

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)
    )
)

Cela appliquera à la fois l'exclusivité (en raison de la CHECK ) et la présence (due à la combinaison de CHECK et FK1 /FK2 ) de l'enfant.

Malheureusement, MS SQL Server ne prend pas en charge les contraintes différées, mais vous pouvez "cacher" toute l'opération derrière des procédures stockées et interdire aux clients de modifier directement les tables.

Seule l'exclusivité peut être appliquée sans contraintes différées :

Le STORAGE_TYPE est un discriminateur de type, généralement un entier pour économiser de l'espace (dans l'exemple ci-dessus, 0 et 1 sont "connus" de votre application et interprétés en conséquence).

Le VAN.STORAGE_TYPE et WAREHOUSE.STORAGE_TYPE peuvent être des colonnes calculées (alias "calculées") pour économiser de l'espace de stockage et éviter le besoin de la CHECK s.

--- MODIFIER ---

Les colonnes calculées fonctionneraient sous SQL Server comme ceci :

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".

Malheureusement, SQL Server nécessite pour une colonne calculée qui est utilisée dans un étranger clé à PERSISTER. D'autres bases de données peuvent ne pas avoir cette limitation (par exemple, les colonnes virtuelles d'Oracle), ce qui peut économiser de l'espace de stockage.