SQL:根据条件防止在表中插入行的触发器

问题描述 投票:0回答:3

我有下表:

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表中插入无效的行来对其进行测试时,会收到一条错误消息,说明

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

有人可以指出我需要对代码进行哪些更改才能获得所需的结果?

sql oracle database-trigger
3个回答
0
投票

我确实认为您在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;
/

希望有所帮助。


0
投票

我将在这里重新排列逻辑:

  1. 然后检查驾驶员是否已经测试过车辆
  2. 检查是否在测试结束日期对于车辆]之前尝试检查(您遗漏的东西)。
  3. 在包含触发器代码的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而不是testing_iddriver_no链接到vehicle_id表。

您的代码中有一些错误:

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

    干杯!


    0
    投票

    您的代码中有一些错误:

    © www.soinside.com 2019 - 2024. All rights reserved.