如何创建验证另一个表中的列的 PL/SQL 行触发器
Posted
技术标签:
【中文标题】如何创建验证另一个表中的列的 PL/SQL 行触发器【英文标题】:How to create a PL/SQL row trigger that validates a column from another table 【发布时间】:2021-09-10 17:34:21 【问题描述】:我有两张桌子:
CREATE TABLE PRODUCT
(
PRODUCT_NAME VARCHAR(40) NOT NULL,
SUPPLIER_NAME VARCHAR(40) NOT NULL,
CATEGORY_NAME VARCHAR(30) NOT NULL,
QUANTITY_PER_UNIT VARCHAR(20) NULL,
UNIT_PRICE NUMBER(10,2) DEFAULT 0,
UNITS_IN_STOCK NUMBER(9) DEFAULT 0,
UNITS_ON_ORDER NUMBER(9) DEFAULT 0,
REORDER_LEVEL NUMBER(9) DEFAULT 0,
DISCONTINUED CHAR(1) DEFAULT 'N',
CONSTRAINT PK_PRODUCT PRIMARY KEY (PRODUCT_NAME),
CONSTRAINT FK_CATEGORY_NAME FOREIGN KEY (CATEGORY_NAME) REFERENCES CATEGORY(CATEGORY_NAME),
CONSTRAINT FK_SUPPLIER_NAME FOREIGN KEY (SUPPLIER_NAME) REFERENCES SUPPLIER(COMPANY_NAME),
CONSTRAINT CK_PRODUCT_UNIT_PRICE CHECK (UNIT_PRICE >= 0),
CONSTRAINT CK_PRODUCT_UNITS_IN_STOCK CHECK (UNITS_IN_STOCK >= 0),
CONSTRAINT CK_PRODUCT_UNITS_ON_ORDER CHECK (UNITS_ON_ORDER >= 0),
CONSTRAINT CK_PRODUCT_REORDER_LEVEL CHECK (REORDER_LEVEL >= 0),
CONSTRAINT CK_PRODUCT_DISCONTINUED CHECK (DISCONTINUED in ('Y','N'))
);
CREATE TABLE SUPPLIER
(
COMPANY_NAME VARCHAR(40) NOT NULL,
CONTACT_NAME VARCHAR(30) NOT NULL,
CONTACT_TITLE VARCHAR(30) NOT NULL,
ADDRESS VARCHAR(60) NOT NULL,
CITY VARCHAR(15) NOT NULL,
REGION VARCHAR(15) NULL,
POSTAL_CODE VARCHAR(10) NOT NULL,
COUNTRY VARCHAR(15) NOT NULL,
PHONE VARCHAR(24) NOT NULL,
FAX VARCHAR(24) NULL,
HOME_PAGE VARCHAR(500) NULL,
TRUSTED_SUPPLIER VARCHAR(3) NULL,
CONSTRAINT PK_SUPPLIER PRIMARY KEY (COMPANY_NAME)
);
我需要创建一个 ROW TRIGGER,它可以自动验证有关要插入数据库的新产品的信息是否由受信任的供应商提供。如果供应商不受信任,则插入必须失败并且必须显示错误消息。
到目前为止,这是我的行触发器:
CREATE OR REPLACE TRIGGER VERIFY_SUPPLIER_TRUST
BEFORE INSERT ON PRODUCT
FOR EACH ROW
DECLARE TRUST VARCHAR(3);
BEGIN
SELECT SUPPLIER.TRUSTED_SUPPLIER
INTO TRUST
FROM SUPPLIER
INNER JOIN PRODUCT ON PRODUCT.SUPPLIER_NAME = SUPPLIER.COMPANY_NAME
WHERE SUPPLIER.TRUSTED_SUPPLIER = 'YES';
IF TRUST = 'NO' THEN
RAISE_APPLICATION_ERROR(20001, 'SUPPLIER NOT TRUSTED');
END IF;
END;
/
但是,当我尝试使用有效和无效的插入语句测试触发器时:
//this should insert without errors
INSERT INTO PRODUCT
(PRODUCT_NAME, SUPPLIER_NAME, CATEGORY_NAME, QUANTITY_PER_UNIT, UNIT_PRICE,
UNITS_IN_STOCK, UNITS_ON_ORDER, REORDER_LEVEL, DISCONTINUED)
VALUES
('Backlestan','Exotic Liquids', 'Beverages', '10 boxes x 30 bags',
11, 20, 0, 27, 'N');
// this should display the trigger's error
INSERT INTO PRODUCT
(PRODUCT_NAME, SUPPLIER_NAME, CATEGORY_NAME, QUANTITY_PER_UNIT, UNIT_PRICE,
UNITS_IN_STOCK, UNITS_ON_ORDER, REORDER_LEVEL, DISCONTINUED)
VALUES
('Backlestan','Another Company', 'Beverages', '10 boxes x 30 bags',
11, 20, 0, 27, 'N');
我收到以下错误:
Error report -
ORA-01422: exact fetch returns more than requested number of rows
ORA-06512: at "RK721.VERIFY_SUPPLIER_TRUST", line 4
ORA-04088: error during execution of trigger 'BH576.VERIFY_SUPPLIER_TRUST'
Error report -
ORA-01422: exact fetch returns more than requested number of rows
ORA-06512: at "RK721.VERIFY_SUPPLIER_TRUST", line 4
ORA-04088: error during execution of trigger 'BH576.VERIFY_SUPPLIER_TRUST'
有人知道如何解决这个错误吗?任何帮助将不胜感激!
【问题讨论】:
【参考方案1】:您的触发器存在多个问题。让我们从 select 语句和剩余代码之间的“关系”开始。在这种特殊情况下,select..
和if...end_if
(目前假设您的选择确实有效,它没有但只是假设)。现在专注于 WHERE 子句。
SELECT SUPPLIER.TRUSTED_SUPPLIER
INTO TRUST
...
WHERE SUPPLIER.TRUSTED_SUPPLIER = 'YES';
IF TRUST = 'NO' THEN ...
由于您的选择仅返回 YES,因此 if 语句永远不会为 True。因此永远不会引发应用程序异常。现在,select
有什么问题。
首先,您正在访问触发触发器的表。虽然在某些情况下您可以侥幸逃脱,但通常会导致ORA-04091: table <table_name> is mutating, trigger/function may not see it。总是避免完全引用触发表是失败的。您使用 :NEW 和/或 :OLD 伪记录引用表数据。其次,您的查询没有按照您的想法进行。它说
为供应商中的每一行选择trusted_supplier列 在 Product 表中至少有 1 行的表和 trusted_supplier 列是“YES”。
但是,INTO 子句要求语句返回正好 1 行。多于 1 行会导致异常,0 行会导致no data found
异常。
最后,raise_application_error statement
存在问题。如果它被执行,它会引发 number 参数...超出范围 异常。第一个参数必须介于 -20999 到 -20000(负数)之间。
那么结果是什么样子的:
create or replace trigger verify_supplier_trust
before insert or update on product
for each row
declare
trust varchar2(3);
begin
select supplier.trusted_supplier
into trust
from supplier
where supplier.company_name = :new.supplier_name
and supplier.trusted_supplier = 'YES';
exception
when no_data_found then
raise_application_error(-20001, 'supplier not trusted');
end;
/
注意事项: 不要使用数据类型 VARCHAR。这是允许的,但 Oracle 建议不要这样做。意味着他们保留随时更改其功能的权利。请改用推荐的 VARCHAR2。 我将触发器更改为在插入或更新时触发。如果在 Insert 上被解雇,只有某人可以更改供应商名称以引用不受信任的供应商,一切都会好起来的。
【讨论】:
现在我得到一个错误:Error(7,36): PLS-00049: bad bind variable 'NEW.COMPANY_NAME'
不是新的,而是:新的。冒号 (:) 是必需的。
我已经包含了冒号。它只是没有出现在错误报告中
抱歉应该是where supplier.company_name = :new.supplier_name
【参考方案2】:
您必须按插入行的 id 进行过滤!
而不是 SUPPLIER.TRUSTED_SUPPLIER = 'YES'
BEGIN
SELECT SUPPLIER.TRUSTED_SUPPLIER
INTO TRUST
FROM SUPPLIER
INNER JOIN PRODUCT ON PRODUCT.SUPPLIER_NAME = SUPPLIER.COMPANY_NAME
WHERE PRODUCT.PRODUCT_NAME = :NEW.PRODUCT_NAME;
【讨论】:
【参考方案3】:试试这个:
create or replace TRIGGER VERIFY_SUPPLIER_TRUST
BEFORE INSERT ON PRODUCT
REFERENCING NEW AS NEW_PRODUCT
FOR EACH ROW
DECLARE TRUST_COUNT NUMBER;
BEGIN
SELECT COUNT(*)
INTO TRUST_COUNT
FROM SUPPLIER
WHERE SUPPLIER.TRUSTED_SUPPLIER = 'YES'
and SUPPLIER.COMPANY_NAME = :NEW_PRODUCT.SUPPLIER_NAME;
IF TRUST_COUNT = 0 THEN
RAISE_APPLICATION_ERROR(20001, 'SUPPLIER NOT TRUSTED');
END IF;
END;
/
对于这组数据:
INSERT INTO SUPPLIER(COMPANY_NAME,CONTACT_NAME,CONTACT_TITLE,ADDRESS,CITY,REGION,POSTAL_CODE,COUNTRY,PHONE,FAX,HOME_PAGE,TRUSTED_SUPPLIER)
VALUES('C1','C1','CT1','A1','R1','PC1','C1','P1','F1','H1','H1','YES');
INSERT INTO SUPPLIER(COMPANY_NAME,CONTACT_NAME,CONTACT_TITLE,ADDRESS,CITY,REGION,POSTAL_CODE,COUNTRY,PHONE,FAX,HOME_PAGE,TRUSTED_SUPPLIER)
VALUES('C2','C1','CT1','A1','R1','PC1','C1','P1','F1','H1','H1','NO');
COMMIT;
此插入有效:
INSERT INTO PRODUCT(PRODUCT_NAME, SUPPLIER_NAME, CATEGORY_NAME, QUANTITY_PER_UNIT, UNIT_PRICE, UNITS_IN_STOCK, UNITS_ON_ORDER, REORDER_LEVEL, DISCONTINUED)
VALUES('Backlestan111','C1', 'Beverages', '10 boxes x 30 bags', 11, 20, 0, 27, 'N'); /* it works*/
但是这个,没有:
INSERT INTO PRODUCT(PRODUCT_NAME, SUPPLIER_NAME, CATEGORY_NAME, QUANTITY_PER_UNIT, UNIT_PRICE, UNITS_IN_STOCK, UNITS_ON_ORDER, REORDER_LEVEL, DISCONTINUED)
VALUES('Backlestan222','C2', 'Beverages', '10 boxes x 30 bags', 11, 20, 0, 27, 'N'); /* it doesn't work*/
【讨论】:
以上是关于如何创建验证另一个表中的列的 PL/SQL 行触发器的主要内容,如果未能解决你的问题,请参考以下文章