SQL:触发以防止根据条件将行插入 table

SQL: trigger to prevent inserting a row into a table based on a condition

我有以下 tables:

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

基本上,车辆在两个日期之间由 driver 秒进行测试。测试完成后,driver 审查车辆。

我想创建一个触发器来防止添加无效评论。如果 driver 在测试结束日期之前审查车辆,则审查无效。如果 driver 评论的车辆不是他驾驶的,则评论也无效。

例如,driver 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 table 中插入无效行来测试它时,我收到一条错误消息,指出

Exact fetch returns more than requested number of rows

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

我认为您的 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;
/

希望对您有所帮助。

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

  1. 检查driver是否测试过车辆,然后
  2. 检查是否在车辆测试结束日期(您遗漏的日期)之前尝试进行审查。

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

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

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

最后,您的 table 结构允许同一个 driver 对同一车辆进行多次测试。如果它 应该 允许这样做,那么 review table 应该 link 到 testing table by testing_id 而不是 driver_novehicle_id.

您的代码中有一些错误:

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

  • 您正在测试 table 中搜索唯一的 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;
/

干杯!!