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;
/
希望对您有所帮助。
我会重新安排这里的逻辑并且:
- 检查driver是否测试过车辆,然后
- 检查是否在车辆测试结束日期(您遗漏的日期)之前尝试进行审查。
在包含触发器代码的 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_no
和 vehicle_id
.
您的代码中有一些错误:
In PL/SQL select 查询必须有一个 INTO
子句,其中从 SELECT 查询中获取的数据存储到一些变量中。 -- 您的代码中缺少该部分
您正在测试 table 中搜索唯一的 driverno
,这将为您提供该 driverno
的所有记录(所有测试的车辆driveno
)。您需要包含 vehicleid
和 driverno
以获取与当前评论相关的测试详细信息。
所以你的触发代码可以重写如下:
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;
/
干杯!!
我有以下 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;
/
希望对您有所帮助。
我会重新安排这里的逻辑并且:
- 检查driver是否测试过车辆,然后
- 检查是否在车辆测试结束日期(您遗漏的日期)之前尝试进行审查。
在包含触发器代码的 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_no
和 vehicle_id
.
您的代码中有一些错误:
In PL/SQL select 查询必须有一个
INTO
子句,其中从 SELECT 查询中获取的数据存储到一些变量中。 -- 您的代码中缺少该部分您正在测试 table 中搜索唯一的
driverno
,这将为您提供该driverno
的所有记录(所有测试的车辆driveno
)。您需要包含vehicleid
和driverno
以获取与当前评论相关的测试详细信息。
所以你的触发代码可以重写如下:
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;
/
干杯!!