2
votes

Comment ajouter une contrainte de clé étrangère à la table A (id, type) référençant l'une des deux tables Table B (id, type) ou Table C (id, type)?

Je cherche à utiliser deux colonnes de la table A comme clés étrangères pour l'une des deux tables: Table B ou Table C.En utilisant les colonnes table_a.item_id et table_a.item_type_id, je veux forcer toutes les nouvelles lignes à avoir un item_id et un item_type_id correspondants dans le tableau B ou le tableau C.

Exemple:

ALTER TABLE [inventory]  
ADD  CONSTRAINT [FK_inventory_sources] FOREIGN KEY ([item_id],[item_type_id])
REFERENCES {[products] ([id],[item_type_id]) OR [recipes] ([id],[item_type_id])}

Je veux pouvoir avoir un tableau d'inventaire où Les employés peuvent saisir des inventaires, que l’article soit une recette ou un produit. Je ne veux pas avoir une table product_inventory et recette_inventory car il y a de nombreuses opérations que je dois faire sur tous les articles de l'inventaire, quels que soient les types d'articles.

Une solution serait de créer une table de référence comme celle-ci :

CREATE TABLE [dbo].[inventory] (
    [id] [bigint] IDENTITY(1,1) NOT NULL,
    [item_id] [smallint] NOT NULL,
    [item_type_id] [tinyint] NOT NULL,
    [count] [float] NOT NULL,
CONSTRAINT [PK_inventory_id] PRIMARY KEY CLUSTERED ([id] ASC)
) ON [PRIMARY]

Cela semble juste très encombrant, et je devrais maintenant ajouter / supprimer des produits / recettes de cette nouvelle table chaque fois qu'ils sont ajoutés / supprimés de leurs tables respectives. (Existe-t-il un moyen automatique d'y parvenir?)

Table CD: Items
+---------+--------------+------------+-----------+
| item_id | item_type_id | product_id | recipe_id |
+---------+--------------+------------+-----------+
|       2 |            1 | NULL       | 2         |
|       3 |            1 | NULL       | 3         |
|       1 |            2 | 1          | NULL      |
+---------+--------------+------------+-----------+

Ce que j'aimerais vraiment faire, c'est quelque chose comme ça ...

Table A: Inventory 
+---------+--------------+-------+
| item_id | item_type_id | count |
+---------+--------------+-------+
|       2 |            1 |    32 |
|       3 |            1 |    24 |
|       1 |            2 |    10 |
+---------+--------------+-------+

Table B: Recipes
+----+--------------+-------------------+-------------+----------------------+
| id | item_type_id |       name        | consistency | gram_to_fluid_ounces |
+----+--------------+-------------------+-------------+----------------------+
|  1 |            1 | Delicious Juice   | thin        | .0048472             |
|  2 |            1 | Ok Tasting Juice  | thin        | .0057263             |
|  3 |            1 | Protein Smoothie  | heavy       | .0049847             |
+----+--------------+-------------------+-------------+----------------------+

Table C: Products
+----+--------------+----------+--------+----------+----------+
| id | item_type_id |   name   | price  | in_stock | is_taxed |
+----+--------------+----------+--------+----------+----------+
|  1 |            2 | Purse    | $200   | TRUE     | TRUE     |
|  2 |            2 | Notebook | $14.99 | TRUE     | TRUE     |
|  3 |            2 | Computer | $1,099 | FALSE    | TRUE     |
+----+--------------+----------+--------+----------+----------+

Other Table: Item_Types
+----+-----------+
| id | type_name |
+----+-----------+
|  1 | recipes   |
|  2 | products  |
+----+-----------+


2 commentaires

Veuillez rechercher l'héritage de modèle dans les bases de données relationnelles et les super / sous-tables.


Vous pouvez vraiment réfléchir à deux fois à sa conception. Il semble que vous fassiez une dénormalisation inutile et seule la réponse @KjetilNordin va droit au but. À titre de suggestion, vous pouvez créer une table d'éléments avec des colonnes de type et de nom et des recettes et produits de menaces comme détails pointant vers un élément


4 Réponses :


1
votes

vous ne pouvez ajouter qu'une seule contrainte pour une colonne ou une paire de colonnes. Pensez aux pommes et aux oranges. Une colonne ne peut pas faire référence à la fois aux oranges et aux pommes. Il doit être orange ou pomme.

En remarque, ceci peut être réalisé d'une manière ou d'une autre avec des colonnes PERSISTED COMPUTED , mais cela n'introduit que des frais généraux et de la complexité.

Vérifiez ceci pour référence


3 commentaires

Merci, mais ce n'est pas utile


Même si vous ne trouvez pas cela utile, il répond complètement à votre question.


Il a depuis ajouté la partie de note d'accompagnement qui est très utile. Merci @Simonare



2
votes

Je pense qu'il y a une faille dans la conception de votre base de données. La meilleure façon de résoudre votre problème réel est de regrouper les recettes et les produits dans un seul tableau. À l'heure actuelle, vous avez une colonne redondante dans chaque table appelée item_type_id. Cette colonne ne vaut rien, sauf si vous avez réellement les éléments dans la même table. Je dis redondant, car il a la même valeur pour absolument toutes les entrées de chaque table.

Vous avez deux options. Si vous ne pouvez pas modifier la conception de la base de données, travaillez sans clés étrangères et sélectionnez la couche logique parmi les tables correctes.

Ou, si vous pouvez modifier la conception de la base de données, faites en sorte que les produits et les recettes existent dans la même table. Vous avez déjà une table item_type, qui peut identifier la catégorisation des articles, il est donc logique de mettre tous les articles dans la même table


3 commentaires

J'ai la possibilité de changer le schéma. Intéressant ... compte tenu des meilleures pratiques, ne serait-il pas "compliqué" de mettre deux types d'éléments dans le même tableau car toutes les colonnes ne seraient pas applicables à chaque entrée? Ou est-ce que je fabrique une montagne à partir d'une taupe?


Tout dépend de la portée et de la taille de votre application, je suppose. Pourquoi avez-vous besoin des données? Honnêtement, la conception des données d'inventaire est assez compliquée. J'ai peut-être répondu un peu prématurément. Mais il doit y avoir un moyen à la fois de pouvoir avoir des articles d'inventaire dans la même table, où les mêmes données s'appliquent, et de conserver également les données supplémentaires nécessaires pour chaque article séparées / normalisées


Dans ce cas, j'aurais peut-être juste travaillé sans les clés étrangères et utilisé une autre couche pour accéder aux tables pertinentes. ou découvrez ce que les deux tables ont en commun, extrayez-la dans une seule table, puis faites en sorte que deux tables distinctes de «données supplémentaires» aient des clés étrangères pour cette table. Parce que deux clés étrangères peuvent pointer facilement sur une colonne, alors que c'est moins courant dans le sens inverse



1
votes

Vous pouvez ajouter des colonnes calculées à la table Inventory :

ALTER TABLE Inventory
    ADD _recipe_item_id AS CASE WHEN item_type_id = 1 THEN item_id END persisted
ALTER TABLE Inventory
    ADD _product_item_id AS CASE WHEN item_type_id = 2 THEN item_id END persisted

Vous pouvez ensuite ajouter deux clés étrangères distinctes aux deux tables, en utilisant ces deux colonnes à la place sur item_id . Je suppose que la colonne item_type_id dans ces deux tables est déjà calculée / contrainte de manière appropriée, mais sinon, vous voudrez peut-être en tenir compte également.

Parce que ces colonnes calculées sont NULL lorsque le mauvais type est sélectionné, et parce que SQL Server ne vérifie pas les contraintes FK si au moins une valeur de colonne est NULL , elles peuvent exister et seulement l'une ou l'autre le sera satisfait à tout moment.


1 commentaires

Idée intéressante .. même si j'ai des tables supplémentaires telles que "Commandes" ou "Factures" qui nécessiteraient également ces deux colonnes. La meilleure approche est peut-être de retravailler la base de données afin que les produits et les recettes partagent la même table



2
votes

Étant donné que vos produits et vos recettes sont stockés séparément et semblent avoir pour la plupart des colonnes séparées, des tableaux d'inventaire séparés sont probablement la bonne approche. par exemple

CREATE TABLE dbo.Products
(
        item_id BIGINT NOT NULL,
        Item_type_id AS 2,
        name VARCHAR(50) NOT NULL,
        Price DECIMAL(10, 4) NOT NULL,
        InStock BIT NOT NULL,
    CONSTRAINT PK_Products__ItemID PRIMARY KEY (item_id),
    CONSTRAINT FK_Products__Item_Type_ID FOREIGN KEY (Item_Type_ID) 
        REFERENCES Item_Type (Item_Type_ID),
    CONSTRAINT FK_Products__ItemID_ItemTypeID FOREIGN KEY (item_id, Item_Type_ID) 
        REFERENCES dbo.Item (item_id, item_type_id)
);

Si vous avez besoin de tous les types combinés, vous pouvez simplement utiliser une vue:

CREATE TABLE dbo.Items
(
        item_id INT IDENTITY(1, 1) NOT NULL 
        Item_type_id INT NOT NULL,
    CONSTRAINT PK_Items__ItemID PRIMARY KEY (item_id),
    CONSTRAINT FK_Items__Item_Type_ID FOREIGN KEY (Item_Type_ID) REFERENCES Item_Type (Item_Type_ID),
    CONSTRAINT UQ_Items__ItemID_ItemTypeID UNIQUE (Item_ID, Item_type_id)
);

SI vous créez un nouveau type d'élément, alors vous besoin de modifier la conception de la base de données de toute façon pour créer une nouvelle table, il vous suffirait donc de modifier la vue en même temps

Une autre possibilité, serait d'avoir une seule table Items, puis d'avoir Products / Les recettes font référence à cela. Vous commencez donc par votre table items, dont chacun a un identifiant unique:

CREATE VIEW dbo.Inventory
AS
    SELECT  Product_id AS item_id,
            2 AS item_type_id,
            [Count]
    FROM    ProductInventory
    UNION ALL
    SELECT  recipe_id AS item_id,
            1 AS item_type_id
            [Count]
    FROM    RecipeInventory;
GO

Notez la clé unique ajoutée sur (item_id, item_type_id) , ceci est important pour l'intégrité référentielle plus tard.

Ensuite, chacune de vos sous-tables a une relation 1: 1 avec ceci, donc votre table de produits deviendrait:

CREATE TABLE dbo.ProductInventory
(
        Product_id INT NOT NULL,
        [count] INT NOT NULL,
    CONSTRAINT FK_ProductInventory__Product_id FOREIGN KEY (Product_id) 
        REFERENCES dbo.Product (Product_id)
);

CREATE TABLE dbo.RecipeInventory
(
        Recipe_id INT NOT NULL,
        [count] INT NOT NULL,
    CONSTRAINT FK_RecipeInventory__Recipe_id FOREIGN KEY (Recipe_id) 
        REFERENCES dbo.Recipe (Recipe_id )
);

Quelques points à noter:

  • item_id est à nouveau la clé primaire, garantissant la relation 1: 1.
  • la colonne calculée item_type_id (comme 2) garantissant que tous les item_type_id sont mis à 2. Il s'agit de la clé car elle permet d'ajouter une contrainte de clé étrangère
  • la clé étrangère sur (item_id, item_type_id) retour à la table items. Cela garantit que vous ne pouvez insérer un enregistrement dans la table des produits que si l'enregistrement d'origine dans la table des articles a un item_type_id de 2.

Une troisième option serait une table unique pour les recettes et les produits et rendrait toutes les colonnes non requises pour les deux nullables. Cette réponse sur les types d'héritage vaut la peine d'être lue.