如何使用 Oracle 触发器(和域 table)代替检查约束来强制执行列范围
How to use Oracle trigger (and a domain table) in place of check constraint to enforce column range
此触发器 (Oracle 12c) 旨在停止在 table(MainTable aka Room)中的行的插入和更新,其中列(价格)大于变量。变量的值取决于另一列(类型)。共有三种“类型”(S、D、K),“类型”允许的最高价格分别为 100、150 和 200。触发器通过引用具有两列和三行的域 table(DomainTable 又名 RoomType)来工作,如下所示 [roomTypeCode(S, D, K), maxPrice(100, 150, 200)] 并确保:
...如果新 MainTable.type = 'S',则新 MainTable.price < DomainTable.maxPrice(S);
...IF new MainTable.type = 'D', THEN new MainTable.price < DomainTable.maxPrice(D);
...如果新 MainTable.type = 'K',则新 MainTable.price < DomainTable.maxPrice(K);
我的尝试无效。
CREATE TRIGGER Room_Type_Price_Range
BEFORE INSERT OR UPDATE ON room
REFERENCING NEW AS newRec
FOR EACH ROW
DECLARE
SELECT maxPrice INTO singleRmMax FROM RoomType WHERE RoomTypeCode = 'S';
SELECT maxPrice INTO doubleRmMax FROM RoomType WHERE RoomTypeCode = 'D';
SELECT maxPrice INTO kingRmMax FROM RoomType WHERE RoomTypeCode = 'K';
BEGIN
IF ( (:newRec.type = 'S' AND :newRec.price > singleRmMax)
OR (:newRec.type = 'D' AND :newRec.price > doubleRmMax)
OR (:newRec.type = 'K' AND :newRec.price > kingRmMax)
)
RAISE_APPLICATION_ERROR(-20001, 'Price constraint violated.
\nCannot Insert/Update in this table.');
END;
我的错误信息:
04098. 00000 - "trigger '%s.%s' is invalid and failed re-validation"
*Cause: A trigger was attempted to be retrieved for execution and was
found to be invalid. This also means that compilation/authorization
failed for the trigger.
*Action: Options are to resolve the compilation/authorization errors,
disable the trigger, or drop the trigger.
感谢您的帮助!
当您创建触发器时,您会看到类似 'compiled with warnings' 或 'Errors: check compiler log' 的消息。那时你可以做 show errors
查看编译失败的原因,或者查看 SQL 开发者的编译器日志 window.
当您插入或更新 the invalid trigger is automatically recompiled,但由于它仍然无效,您会收到 ORA-04098 错误。您仍然可以通过查询 user_errors
视图来查看问题所在:
select line, position, text
from user_errors
where type = 'TRIGGER'
and name = 'ROOM_TYPE_PRICE_RANGE'
order by sequence;
你的代码给出了三个错误;仅显示每个的第一行:
LINE POSITION TEXT
---- -------- -------------------------------------------------------------------------------------------------
2 5 PLS-00103: Encountered the symbol "SELECT" when expecting one of the following:
10 9 PLS-00103: Encountered the symbol "RAISE_APPLICATION_ERROR" when expecting one of the following:
11 50 PLS-00103: Encountered the symbol ";" when expecting one of the following:
正如 David Faber 在评论中指出的那样,第一个错误是因为您的声明部分中有一个 select 语句;也许复习 the structure of a subprogram 在这一点上会有用。
第二个错误是因为您的 IF
没有 THEN
关键字,第三个错误是因为您也没有 END IF
。只需清理您必须声明的内容并正确填充变量,您就会得到如下内容:
DECLARE
singleRmMax RoomType.MaxPrice%TYPE;
doubleRmMax RoomType.MaxPrice%TYPE;
kingRmMax RoomType.MaxPrice%TYPE;
BEGIN
SELECT maxPrice INTO singleRmMax FROM RoomType WHERE RoomTypeCode = 'S';
SELECT maxPrice INTO doubleRmMax FROM RoomType WHERE RoomTypeCode = 'D';
SELECT maxPrice INTO kingRmMax FROM RoomType WHERE RoomTypeCode = 'K';
IF ( (:newRec.type = 'S' AND :newRec.price > singleRmMax)
OR (:newRec.type = 'D' AND :newRec.price > doubleRmMax)
OR (:newRec.type = 'K' AND :newRec.price > kingRmMax)
) THEN
RAISE_APPLICATION_ERROR(-20001, 'Price constraint violated.
\nCannot Insert/Update in this table.');
END IF;
END;
虽然你真的不需要三个变量,你可以查询你感兴趣的房间类型:
DECLARE
roomTypeMax RoomType.MaxPrice%TYPE;
BEGIN
SELECT maxPrice INTO roomTypeMax
FROM RoomType
WHERE RoomTypeCode = :newRec.type;
IF :newRec.price > roomTypeMax THEN
RAISE_APPLICATION_ERROR(-20001,
'Price constraint violated. Cannot Insert/Update in this table.');
END IF;
END;
我还从错误消息中删除了 \n
,因为插入内容可能会将其视为两个文字字符而不是换行符。
您可能还想考虑捕获 no_data_found
并提出您自己的异常,因为这表明新房型不存在,因此在任何价格下均无效。
此触发器 (Oracle 12c) 旨在停止在 table(MainTable aka Room)中的行的插入和更新,其中列(价格)大于变量。变量的值取决于另一列(类型)。共有三种“类型”(S、D、K),“类型”允许的最高价格分别为 100、150 和 200。触发器通过引用具有两列和三行的域 table(DomainTable 又名 RoomType)来工作,如下所示 [roomTypeCode(S, D, K), maxPrice(100, 150, 200)] 并确保:
...如果新 MainTable.type = 'S',则新 MainTable.price < DomainTable.maxPrice(S);
...IF new MainTable.type = 'D', THEN new MainTable.price < DomainTable.maxPrice(D);
...如果新 MainTable.type = 'K',则新 MainTable.price < DomainTable.maxPrice(K);
我的尝试无效。
CREATE TRIGGER Room_Type_Price_Range
BEFORE INSERT OR UPDATE ON room
REFERENCING NEW AS newRec
FOR EACH ROW
DECLARE
SELECT maxPrice INTO singleRmMax FROM RoomType WHERE RoomTypeCode = 'S';
SELECT maxPrice INTO doubleRmMax FROM RoomType WHERE RoomTypeCode = 'D';
SELECT maxPrice INTO kingRmMax FROM RoomType WHERE RoomTypeCode = 'K';
BEGIN
IF ( (:newRec.type = 'S' AND :newRec.price > singleRmMax)
OR (:newRec.type = 'D' AND :newRec.price > doubleRmMax)
OR (:newRec.type = 'K' AND :newRec.price > kingRmMax)
)
RAISE_APPLICATION_ERROR(-20001, 'Price constraint violated.
\nCannot Insert/Update in this table.');
END;
我的错误信息:
04098. 00000 - "trigger '%s.%s' is invalid and failed re-validation"
*Cause: A trigger was attempted to be retrieved for execution and was
found to be invalid. This also means that compilation/authorization
failed for the trigger.
*Action: Options are to resolve the compilation/authorization errors,
disable the trigger, or drop the trigger.
感谢您的帮助!
当您创建触发器时,您会看到类似 'compiled with warnings' 或 'Errors: check compiler log' 的消息。那时你可以做 show errors
查看编译失败的原因,或者查看 SQL 开发者的编译器日志 window.
当您插入或更新 the invalid trigger is automatically recompiled,但由于它仍然无效,您会收到 ORA-04098 错误。您仍然可以通过查询 user_errors
视图来查看问题所在:
select line, position, text
from user_errors
where type = 'TRIGGER'
and name = 'ROOM_TYPE_PRICE_RANGE'
order by sequence;
你的代码给出了三个错误;仅显示每个的第一行:
LINE POSITION TEXT
---- -------- -------------------------------------------------------------------------------------------------
2 5 PLS-00103: Encountered the symbol "SELECT" when expecting one of the following:
10 9 PLS-00103: Encountered the symbol "RAISE_APPLICATION_ERROR" when expecting one of the following:
11 50 PLS-00103: Encountered the symbol ";" when expecting one of the following:
正如 David Faber 在评论中指出的那样,第一个错误是因为您的声明部分中有一个 select 语句;也许复习 the structure of a subprogram 在这一点上会有用。
第二个错误是因为您的 IF
没有 THEN
关键字,第三个错误是因为您也没有 END IF
。只需清理您必须声明的内容并正确填充变量,您就会得到如下内容:
DECLARE
singleRmMax RoomType.MaxPrice%TYPE;
doubleRmMax RoomType.MaxPrice%TYPE;
kingRmMax RoomType.MaxPrice%TYPE;
BEGIN
SELECT maxPrice INTO singleRmMax FROM RoomType WHERE RoomTypeCode = 'S';
SELECT maxPrice INTO doubleRmMax FROM RoomType WHERE RoomTypeCode = 'D';
SELECT maxPrice INTO kingRmMax FROM RoomType WHERE RoomTypeCode = 'K';
IF ( (:newRec.type = 'S' AND :newRec.price > singleRmMax)
OR (:newRec.type = 'D' AND :newRec.price > doubleRmMax)
OR (:newRec.type = 'K' AND :newRec.price > kingRmMax)
) THEN
RAISE_APPLICATION_ERROR(-20001, 'Price constraint violated.
\nCannot Insert/Update in this table.');
END IF;
END;
虽然你真的不需要三个变量,你可以查询你感兴趣的房间类型:
DECLARE
roomTypeMax RoomType.MaxPrice%TYPE;
BEGIN
SELECT maxPrice INTO roomTypeMax
FROM RoomType
WHERE RoomTypeCode = :newRec.type;
IF :newRec.price > roomTypeMax THEN
RAISE_APPLICATION_ERROR(-20001,
'Price constraint violated. Cannot Insert/Update in this table.');
END IF;
END;
我还从错误消息中删除了 \n
,因为插入内容可能会将其视为两个文字字符而不是换行符。
您可能还想考虑捕获 no_data_found
并提出您自己的异常,因为这表明新房型不存在,因此在任何价格下均无效。