【问题标题】:How to add foreign key constraint to Table A (id, type) referencing either of two tables Table B (id, type) or Table C (id, type)?如何将外键约束添加到表 A(id,type)引用两个表 Table B(id,type)或表 C(id,type)中的一个?
【发布时间】:2019-02-06 02:55:33
【问题描述】:

我希望使用表 A 中的两列作为两个表之一的外键:表 B 或表 C。使用列 table_a.item_id 和 table_a.item_type_id,我想强制任何新行具有表 B 或表 C 中匹配的 item_id 和 item_type_id。

示例:

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

我希望能够有一个库存表,员工可以在其中输入库存计数,无论项目是配方还是产品。我不想拥有 product_inventory 和 recipe_inventory 表,因为无论项目类型如何,我都需要对所有库存项目执行许多操作。

一种解决方案是像这样创建一个参考表:

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

这看起来很麻烦,而且我现在需要从这个新表中添加/删除产品/食谱,只要它们从各自的表中添加/删除。 (有没有自动的方法来实现这一点?)

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]

我真正想做的是这样的事情......

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

也许没有我所描述的解决方案,所以如果您有任何想法可以让我保持相同/相似的架构,我绝对愿意听取他们的意见! 谢谢:)

【问题讨论】:

  • 请在关系型数据库和父/子表中搜索模型继承。
  • 你真的可以三思而后行他的设计。看起来你正在做一个不必要的非规范化,只有@KjetilNordin 的回答才是重点。作为建议,您可以创建一个项目表,其中包含类型和名称列以及威胁配方和产品作为指向项目的详细信息

标签: sql sql-server azure-sql-database


【解决方案1】:

由于您的产品和食谱是分开存储的,并且似乎大部分都有单独的列,因此单独的库存表可能是正确的方法。例如

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

如果您需要组合所有类型,您可以简单地使用视图:

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

如果你创建了一个新的 item_type,那么无论如何你都需要修改数据库设计来创建一个新表,所以你只需要同时修改视图

另一种可能性是有一个 Items 表,然后让 Products/Recipes 引用它。所以你从你的 items 表开始,每个表都有一个唯一的 ID:

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

注意(item_id, item_type_id) 上添加的唯一键,这对于以后的参照完整性很重要。

那么您的每个子表都与此有 1:1 的关系,因此您的产品表将变为:

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

需要注意的几点:

  • item_id 再次成为主键,确保 1:1 关系。
  • 计算列 item_type_id(为 2)确保所有 item_type_id 设置为 2。这是关键,因为它允许添加外键约束
  • (item_id, item_type_id) 上的外键返回项目表。这确保了您只能在 items 表中的原始记录的 item_type_id 为 2 的情况下向 product 表中插入一条记录。

第三个选项是食谱和产品的单个表,并使不需要的任何列都可以为空。 This answer on types of inheritance 值得一读。

【讨论】:

【解决方案2】:

我认为您的数据库设计存在缺陷。解决实际问题的最佳方法是将 Recipies 和 products 作为一个表。现在,每个表中都有一个名为 item_type_id 的冗余列。该列没有任何价值,除非您实际上将项目放在同一个表中。我说的是多余的,因为它对于每个表中的每个条目都具有相同的值。

你有两个选择。如果您无法更改数据库设计,请在没有外键的情况下工作,并从正确的表中选择逻辑层。

或者,如果您可以更改数据库设计,使产品和食谱存在于同一个表中。你已经有一个 item_type 表,它可以识别项目的分类,所以将所有项目放在同一个表中是有意义的

【讨论】:

  • 我确实有能力更改架构。有趣...考虑到最佳实践,将两个项目类型放在同一个表中不会“混乱”,因为并非每一列都适用于每个条目?还是我是在鼹鼠山上造山?
  • 我猜这完全取决于您的应用程序的范围和大小。你需要这些数据做什么?老实说,库存数据设计相当复杂。我可能回答得有点过早了。但是必须有一种方法,既能够在同一个表中拥有库存项目,应用相同的数据,又能保持每个项目所需的额外数据分离/规范化
  • 在这种情况下,我可能只是在没有外键的情况下工作,并使用另一层来访问相关表。或者找出这两个表的共同点,将其提取到一个表中,然后使两个单独的“额外数据”表具有该表的外键。因为两个外键可能很容易指向一列,而相反的方式不太常见
【解决方案3】:

您只能为一列或一对列添加一个约束。想想苹果和橘子。列不能同时引用橘子和苹果。它必须是橙色或苹果。

附带说明,这可以通过PERSISTED COMPUTED columns 以某种方式实现,但是它只会引入开销和复杂性。

Check This for Reference

【讨论】:

  • 谢谢,但这没有帮助
  • 即使您觉得这对您没有帮助,他也会完全回答您的问题。
  • 他已经添加了非常有用的旁注部分。谢谢@Simonare
【解决方案4】:

您可以在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

然后,您可以使用这两列而不是 item_id,向这两个表添加两个单独的外键。我假设这两个表中的 item_type_id 列已经被适当地计算/约束,但如果不是,您可能也需要考虑这一点。

因为选择错误的类型时,这些计算的列是 @987654325 @,并且由于SQL Server不会检查FK约束,如果至少一个列值为 @987654326 @,它们都可以存在,并且只有一个或另一个将会随时满足。

【讨论】:

  • 有趣的想法..虽然如果我有额外的表格,如“订单”或“发票”,也需要这两列。也许最好的方法是重新设计数据库,以便 Products 和 Recipes 共享同一个表
猜你喜欢
  • 2011-12-08
  • 2013-01-04
  • 2012-11-25
  • 2010-11-04
  • 2019-02-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-27
相关资源
最近更新 更多