Devression
Devression

Reputation: 457

How to create a PL/SQL row trigger that validates a column from another table

I have two tables:

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

I need to create a ROW TRIGGER that automatically verifies if information about a new product to be inserted into the database is supplied by a trusted supplier. If a supplier is not trusted, then the insertion must fail and an error message must be displayed.

Here is my row trigger so far:

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

However when I try to test the trigger with valid and non-valid insert statements:

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

I receive these errors:

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'

Does anybody know how to fix this error? Any help would be greatly appreciated!

Upvotes: 0

Views: 108

Answers (3)

Belayer
Belayer

Reputation: 14932

There are several issues with your trigger. Let's begin with the 'relationship' between a select statement and remaining code. In this particular case the select.. and the if...end_if (for the moment assume your select actually works, it does not but just assume). Now concentrate on the WHERE clause.

SELECT SUPPLIER.TRUSTED_SUPPLIER
    INTO TRUST
    ...
    WHERE SUPPLIER.TRUSTED_SUPPLIER = 'YES';

IF TRUST = 'NO' THEN ...

Since your select returns ONLY YES the if statement will never be True. Therefore the application exception can never be raised. Now, what are the issues with the select.
Well first you are accessing the table that the trigger is fired upon. While in some cases you can get away with it but usually it results in an ORA-04091: table <table_name> is mutating, trigger/function may not see it. It is bust to always avoid referencing the triggering table altogether. You reference the table data with the :NEW and/or :OLD pseudo records. Secondly, your query is not doing what you think it is. It says

Select the trusted_supplier column for every row in the Supplier table that has at least 1 row in the Product table and the trusted_supplier column is 'YES'.

However the INTO clause requires the statement to return exactly 1 row. More that 1 row results in the exception, and 0 rows results in a no data found exception.
Finally there is an issue with the raise_application_error statement. If it were executed it would raise an number argument...is out of range exception. The first parameter must be between -20999 to -20000 (Negative number). So what does the result look like:

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

NOTES:
DO not use data type VARCHAR. It is allowed but Oracle recommends against it. Means they are reserving the right to change what it does at any time. Use the recommended VARCHAR2 instead.
I change the trigger to fire on either Insert or Update. If fired on Insert only someone CAN change the supplier_name to reference a non-trusted supplier and all would be fine.

Upvotes: 2

Kick
Kick

Reputation: 13

Try this:

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

For this set of data:

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;

This insert 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('Backlestan111','C1', 'Beverages', '10 boxes x 30 bags',  11, 20, 0, 27, 'N'); /* it works*/

But this one, doesn't:

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

Upvotes: 0

You must filter by the inserted row's id!

And not by 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;

Upvotes: 0

Related Questions