【问题标题】:SQL: trigger to prevent inserting a row into a table based on a conditionSQL:触发器以防止根据条件将行插入表中
【发布时间】:2019-10-25 12:25:21
【问题描述】:

我有以下表格:

CREATE TABLE review 
(
    review_id NUMBER(2) NOT NULL,
    review_date DATE NOT NULL,
    review_rating NUMBER(1) NOT NULL,
    driver_no NUMBER(2) NOT NULL,
    vehicle_id NUMBER(3) NOT NULL
);

CREATE TABLE testing 
(
    testing_id NUMBER(2) NOT NULL,
    testing_start DATE NOT NULL,
    testing_end DATE NOT NULL
    driver_no NUMBER(2) NOT NULL,
    vehicle_id NUMBER(3) NOT NULL
);

基本上,驾驶员会在两个日期之间对车辆进行测试。测试完成后,驾驶员对车辆进行检查。

我想创建一个触发器,以防止添加无效评论。如果驾驶员在测试结束日期之前对车辆进行了审查,则该审查无效。如果驾驶员对他没有驾驶过的车辆进行评论,则该评论也无效。

例如,驾驶员 1 在 2019 年 2 月 1 日至 2019 年 2 月 7 日期间测试车辆 7。如果添加了 2019 年 2 月 5 日的评论,我希望触发器阻止插入此评论。此外,如果添加了对车辆 5 的评论(当车辆 7 是正在测试的车辆时),我希望触发器防止插入。

这是我目前所拥有的:

CREATE OR REPLACE TRIGGER review_check_validity
AFTER INSERT ON review
FOR EACH ROW
BEGIN
    SELECT testing_start
    FROM testing
    WHERE driver_no = :new.driver_no;

    SELECT vehicle_id
    FROM testing
    WHERE driver_no = :new.driver_no;

    IF :new.review_date < testing_end THEN
    raise_application_error(-20000, 'Review date cannot be before 
    testing end date');

    END IF;

    IF :new.vehicle_id != vehicle_id THEN
    raise_application_error(-20000, 'Driver has never driven this 
    vehicle');

    END IF;
END;
/

触发器编译没有任何错误 - 但是当我尝试通过在 REVIEW 表中插入无效行来测试它时,我收到一条错误消息,指出

精确提取返回的行数超过请求的行数

谁能指出我需要对我的代码进行哪些更改才能达到预期的结果?

【问题讨论】:

  • 首先,您选择必须返回 INTO 某个变量。 - oracletutorial.com/plsql-tutorial/plsql-select-into 此外,如果您的查询返回多于一行,则不能这样做。
  • 如果司机多次检查或测试同一辆车会怎样?
  • @GordonLinoff 假设驾驶员不会两次测试同一辆车。当然,这不是一个现实的假设,但它是问题中给我的限制之一。

标签: sql oracle database-trigger


【解决方案1】:

我会重新安排这里的逻辑:

  1. 检查司机是否测试过车辆,然后
  2. 检查是否在车辆的测试结束日期之前尝试了审核(您已经遗漏了一些内容)。

在包含触发器代码的 Oracle PL/SQL 中,您不能只使用SELECT。你必须SELECT INTO 一个变量。然后你就可以在你的逻辑中使用这个变量了。

同样重要的是,当您SELECT INTO 一个变量时,查询只能返回一个结果。多行会触发你遇到的错误。

CREATE OR REPLACE TRIGGER review_check_validity
AFTER INSERT ON review
FOR EACH ROW
DECLARE
    testEnd DATE;
    vehicleTestCount NUMBER;
BEGIN

    SELECT COUNT(*)
      INTO vehicleTestCount
      FROM testing
      WHERE vehicle_id = :new.vehicle_id;

    IF vehicleTestCount = 0 THEN
      raise_application_error(-20000, 'Driver has never driven this vehicle');
    END IF;

    -- Assumes one test per driver per vehicle
    SELECT testing_end
      INTO testEnd
      FROM testing
      WHERE driver_no = :new.driver_no
        AND vehicle_id = :new.vehicle_id;

    IF :new.review_date < testEnd THEN
      raise_application_error(-20000, 'Review date cannot be before 
    testing end date');

    END IF;

END;
/

最后,您的表结构允许同一驾驶员对同一车辆进行多次测试。如果它应该允许这样做,那么review 表应该通过testing_id 而不是driver_novehicle_id 链接到testing 表。

【讨论】:

    【解决方案2】:

    我确实认为您应该在 SELECT 提取中包含一个 INTO 子句。我在您的查询中添加了一些注释以帮助您清除它。

    CREATE OR REPLACE TRIGGER review_check_validity
    AFTER INSERT ON review
    FOR EACH ROW
    
    -- Need to declare variables for usage in your SELECT fetches.
    DECLARE
    var_revdate number; -- or date, if applicable
    var_revvehi number; -- or varchar(n), if applicable
    
    BEGIN
        -- In here you should have an INTO clause to assign your date parameter into 
        -- the predefined variable. As well, you need to ensure this fetch will provide
        -- only one row.
    
        SELECT testing_start INTO var_revdate -- why are you fetching "testing_start"?
            FROM testing
                WHERE driver_no = :new.driver_no;
    
        -- Same case, it can only retrieve one row. If you need to do more than one row, 
        -- you may need to use a BULK & a LOOP.
    
        SELECT vehicle_id INTO var_vehicid
            FROM testing
                WHERE driver_no = :new.driver_no;
    
        IF :new.review_date < testing_end THEN
        raise_application_error(-20000, 'Review date cannot be before testing end date');
    
        END IF;
    
        IF :new.vehicle_id != var_vehicid THEN
        raise_application_error(-20000, 'Driver has never driven this vehicle');
    
        END IF;
    END;
    /
    

    希望对您有所帮助。

    【讨论】:

      【解决方案3】:

      你的代码有几个错误:

      • 在 PL/SQL 中,选择查询必须有一个 INTO 子句,其中从 SELECT 查询获取的数据存储到一些变量中。 -- 您的代码中缺少该部分

      • 您正在测试表中搜索唯一的driverno,这将为您提供该driverno 的所有记录(该driveno 测试的所有车辆)。您需要包含vehicleiddriverno 以获取与当前评论相关的测试详细信息。

      所以你的触发代码可以重写如下:

      CREATE OR REPLACE TRIGGER REVIEW_CHECK_VALIDITY 
      BEFORE INSERT ON REVIEW -- USING BEFORE INSERT TRIGGER TO AVOID ANY UNDOs
          FOR EACH ROW
      DECLARE
          LV_TEST_END_DATE   DATE;
          LV_TEST_COUNT      NUMBER;
      BEGIN
          -- FETCHING RELEVANT DATA USING SINGLE QUERY
          SELECT
              COUNT(1),
              MAX(TESTING_END) -- PLEASE HANDLE THE SCENARIO WHERE THE TESTING END DATE IS NULL
          INTO
              LV_TEST_COUNT,
              LV_TEST_END_DATE
          FROM
              TESTING
          WHERE
              VEHICLE_ID = :NEW.VEHICLE_ID
              AND DRIVER_NO = :NEW.DRIVER_NO;
      
          IF LV_TEST_COUNT = 0 THEN
              RAISE_APPLICATION_ERROR(-20000, 'Driver has never driven this vehicle');
          ELSIF :NEW.REVIEW_DATE < LV_TEST_END_DATE THEN
              RAISE_APPLICATION_ERROR(-20000, 'Review date cannot be before testing end date');
          END IF;
      
      END;
      /
      

      干杯!!

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-10-12
        • 2013-03-15
        • 2017-04-26
        • 1970-01-01
        • 2011-03-08
        相关资源
        最近更新 更多