【问题标题】:Integrity constraint for tables not immediately related不直接相关的表的完整性约束
【发布时间】:2013-12-15 16:37:21
【问题描述】:

我是一名 SQL 初学者,但我不知道如何为以下情况正确创建完整性约束:

该架构描述了一个交付系统 - 每个餐厅都提供一些可以交付给客户的商品(在可见架构之外)。

问题来自in_delivery 表 - 菜单中的项目通过此表与交付一起注册。根据目前的情况,可以将menu_item 添加到delivery,这是由restaurant 完成的,但该餐厅可能不提供menu_item!

当插入in_delivery 时,我需要以某种方式检查Menu_Item_MenuItem_ID 是否存在于offers 中,是否有Restaurant_RestaurantID 等于RestaurantID 中的Delivery 与表关联。

我不知道我是否可以在这里使用外键,因为这些表不是“相邻的”..

想到的是在in_delivery 中有一个RestaurantID,这将是RestaurantDelivery 的外键。然后我可以在offers 中找到它。有没有更好的办法?

感谢您的帮助

【问题讨论】:

    标签: sql oracle database-design constraints


    【解决方案1】:

    您可以通过以下更改强制执行您的约束:

    1. in_delivery 表中添加restaurant_id
    2. delivery (delivery_id, restaurant_id) 上添加唯一约束(3 需要。)
    3. 将外键从in_delivery -> delivery改为指向(delivery_id, restaurant_id)
    4. 将外键从in_delivery -> menu_item更改为in_delivery -> offers

    【讨论】:

      【解决方案2】:

      您也可以使用触发器来检查约束:

      SQL Fiddle

      Oracle 11g R2 架构设置

      CREATE TABLE Restaurants (
        RestaurantID NUMBER(2) PRIMARY KEY,
        Name         VARCHAR2(30) NOT NULL
      )
      /
      
      INSERT INTO Restaurants
                SELECT 1, 'Soylent Green Express' FROM DUAL
      UNION ALL SELECT 2, 'Helga''s House of Ribs' FROM DUAL
      /
      
      CREATE TABLE Menu_Items (
        Menu_Item_ID NUMBER(2) PRIMARY KEY,
        Name         VARCHAR2(20) NOT NULL
      )
      /
      
      INSERT INTO Menu_Items
                SELECT 1, 'Soylent Green' FROM DUAL
      UNION ALL SELECT 2, 'Ribs' FROM DUAL
      /
      
      CREATE TABLE Offers (
        RestaurantID NUMBER(2),
        Menu_Item_ID NUMBER(2),
        PRIMARY KEY ( RestaurantID, Menu_Item_ID ),
        FOREIGN KEY ( RestaurantID ) REFERENCES Restaurants ( RestaurantID ),
        FOREIGN KEY ( Menu_Item_ID ) REFERENCES Menu_Items ( Menu_Item_ID )
      )
      /
      
      INSERT INTO Offers
                SELECT 1, 1 FROM DUAL
      UNION ALL SELECT 2, 2 FROM DUAL
      /
      
      CREATE TABLE Deliveries (
        RestaurantID NUMBER(2) NOT NULL,
        Delivery_ID  NUMBER(2) PRIMARY KEY,
        FOREIGN KEY ( RestaurantID ) REFERENCES Restaurants ( RestaurantID )
      )
      /
      
      INSERT INTO Deliveries
                SELECT 1, 1 FROM DUAL
      UNION ALL SELECT 2, 2 FROM DUAL
      /
      
      CREATE TABLE in_delivery (
        Delivery_ID NUMBER(2),
        Menu_Item_ID NUMBER(2),
        PRIMARY KEY ( Delivery_ID, Menu_Item_ID ),
        FOREIGN KEY ( Delivery_ID ) REFERENCES Deliveries ( Delivery_ID ),
        FOREIGN KEY ( Menu_Item_ID ) REFERENCES Menu_Items ( Menu_Item_ID )
      )
      /
      

      为了便于阅读,我创建了两个有用的函数(您可能希望在其中进行一些异常处理):

      CREATE OR REPLACE FUNCTION get_Delivery_RestaurantID (
        p_Delivery_ID Deliveries.Delivery_ID%TYPE
      ) RETURN Restaurants.RestaurantID%TYPE
      AS
        v_RestaurantID Restaurants.RestaurantID%TYPE;
      BEGIN
        SELECT RestaurantID
        INTO   v_RestaurantID
        FROM   Deliveries
        WHERE  Delivery_ID = p_Delivery_ID;
      
        RETURN v_RestaurantID;
      END get_Delivery_RestaurantID;
      /
      
      CREATE OR REPLACE FUNCTION does_Restaurant_Offer_Item (
        p_RestaurantID Restaurants.RestaurantID%TYPE,
        p_Menu_Item_ID Menu_Items.Menu_Item_ID%TYPE
      ) RETURN NUMBER
      AS
        v_exists NUMBER(1);
      BEGIN
        SELECT CASE WHEN EXISTS ( SELECT 1 
                                  FROM   Offers
                                  WHERE  RestaurantID = p_RestaurantID
                                  AND    Menu_Item_ID = p_Menu_Item_ID
                                )
               THEN 1
               ELSE 0
               END
        INTO   v_exists
        FROM   DUAL;
      
        RETURN v_exists;
      END does_Restaurant_Offer_Item;
      /
      

      然后只需向表中添加一个触发器以检查餐厅是否提供该项目,如果没有,则引发异常。

      CREATE TRIGGER check_Valid_Delivery_Item
      BEFORE INSERT OR UPDATE OF Delivery_ID, Menu_Item_ID
      ON in_delivery
      FOR EACH ROW
      BEGIN
         IF does_restaurant_Offer_Item( get_Delivery_RestaurantID( :new.Delivery_ID ), :new.Menu_Item_ID ) = 0
         THEN
           RAISE_APPLICATION_ERROR (-20100, 'Invalid Delivery Item');
         END IF;
      END check_Valid_Delivery_Item;
      /
      
      INSERT INTO in_delivery VALUES( 1, 1 )
      /
      
      INSERT INTO in_delivery VALUES( 2, 2 )
      /
      

      查询 1

      SELECT * FROM in_delivery
      

      Results

      | DELIVERY_ID | MENU_ITEM_ID |
      |-------------|--------------|
      |           1 |            1 |
      |           2 |            2 |
      

      如果你尝试这样做:

      INSERT INTO in_delivery VALUES( 1, 2 );
      

      然后你得到:

      ORA-20100: Invalid Delivery Item ORA-06512: at "USER_4_F9593.CHECK_VALID_DELIVERY_ITEM", line 4 ORA-04088: error during execution of trigger 'USER_4_F9593.CHECK_VALID_DELIVERY_ITEM' : INSERT INTO in_delivery VALUES( 1, 2 )
      

      【讨论】:

      • 此解决方案并不完全可靠。例如,触发器可能会给出假阴性或假阳性结果,因为它不会“看到”另一个事务正在进行的并发更新(例如,更改与交付或菜单项相关的餐厅)。为了更加稳健,基于触发器的解决方案需要使用锁定来防止此类更新。
      • 是的,对于 OP 来说,在使用这个选项之前应该考虑这是否可能;但对我来说(基于我对数据集所做的假设)似乎不太可能会得到这些更改,因为我不知道有任何提供外卖的餐厅会突然转移到另一家餐厅。 @Vincent 的回答是更好的选择,但如果表格的设计是不可变的,那么这是不可能的。
      猜你喜欢
      • 2023-03-28
      • 2022-01-17
      • 2021-11-01
      • 2012-07-11
      • 2012-01-31
      • 2021-08-03
      • 1970-01-01
      • 2015-04-11
      • 2020-01-14
      相关资源
      最近更新 更多