CREATE TABLE INVENTORY(
INVENTORY_ID NUMBER(6) PRIMARY KEY,
ITEM_NAME VARCHAR2(255),
QUANTITY NUMBER,
PRICE NUMBER,
ITEM_SIZE VARCHAR(255),
INVENTORY_VALUE NUMBER);
===
CREATE TABLE INVENTORY_AUDIT(
DATE_CHANGED DATE,
USER_NAME VARCHAR2(30),
INV_ID NUMBER(6),
OLD_QUANTITY NUMBER,
NEW_QUANTITY NUMBER,
CONSTRAINT fk_INV_ID
FOREIGN KEY (INV_ID)
REFERENCES INVENTORY (INVENTORY_ID));
===
CREATE OR REPLACE TRIGGER INV_CHG
BEFORE UPDATE OF QUANTITY
ON INVENTORY
FOR EACH ROW
BEGIN
IF :NEW.QUANTITY <> :OLD.QUANTITY THEN
INSERT INTO INVENTORY_AUDIT
VALUES (GETDATE(), v('APP_USER'), :NEW.INVENTORY_ID, :OLD.QUANTITY, :NEW.QUANTITY)
END IF;
END;
===
ORA-24344: success with compilation error
ORA-06512: at "SYS.WWV_DBMS_SQL_APEX_190200", line 592
ORA-06512: at "SYS.DBMS_SYS_SQL", line 1658
ORA-06512: at "SYS.WWV_DBMS_SQL_APEX_190200", line 578
ORA-06512: at "APEX_190200.WWV_FLOW_DYNAMIC_EXEC", line 2057
3. ON INVENTORY
4. FOR EACH ROW
5. BEGIN
6. IF :NEW.QUANTITY <> :OLD.QUANTITY THEN
7. INSERT INTO INVENTORY_AUDIT
===
I tried to create trigger that triggered by inventory table's quantity change.
But it throws an error and syntax looks fine for me.
I tried without IF whole statement but still had same problem.
That means my create trigger statement has problem right?
But I cannot find it.
Two things:
missing semi-colon at the end of the INSERT statement
use sysdate instead of getdate
Here's how:
SQL> CREATE OR REPLACE TRIGGER INV_CHG
2 BEFORE UPDATE OF QUANTITY
3 ON INVENTORY
4 FOR EACH ROW
5 BEGIN
6 IF :NEW.QUANTITY <> :OLD.QUANTITY THEN
7 INSERT INTO INVENTORY_AUDIT
8 VALUES (sysdate, --> sysdate instead of getdate()
9 v('APP_USER'),
10 :NEW.INVENTORY_ID,
11 :OLD.QUANTITY,
12 :NEW.QUANTITY
13 ); --> missing semi-colon here
14 END IF;
15 END;
16 /
Trigger created.
SQL>
Related
I need some help for figuring out how to use the CURRENT_DATE if the p_start_time is NULL when doing an insert.
The code below is what I have figured out so far (except for the insert statement). The code I need help for is the last code block.
Using PL/SQL btw.
create or replace procedure BEGIN_TRIP_SP (
p_trip_id OUT INTEGER, -- an output parameter
p_bicycle_id IN INTEGER, -- Must not be NULL. Must match value value in BC_BICYCLE and BC_DOCK tables.
p_start_time IN DATE, -- If NULL, use CURRENT_DATE system date value
p_membership_id IN INTEGER -- Must not be NULL. Must match value in BC_MEMBERSHIP table.
)
IS
lv_membership_id_exist INTEGER;
lv_bicycle_id_exist INTEGER;
lv_Error_Message VARCHAR2(200);
ex_Exception EXCEPTION;
BEGIN
IF p_bicycle_id IS NULL THEN
lv_Error_Message := 'Missing mandatory value for bicycle id in BEGIN_TRIP_SP. No trip added.';
RAISE ex_Exception;
END IF;
IF p_start_time IS NULL THEN
NVL(trip_start_time, '08-03-2022')
END IF;
INSERT INTO bc_trip (
Trip_id,
Bicycle_id,
Trip_start_time,
Membership_id
)
VALUES (
p_trip_id,
p_bicycle_id,
NVL(p_start_time, current_date),
p_membership_id
);
See if this helps; I presumed trip_id is somehow automatically generated so you'll be just returning its value to the caller.
Sample table:
SQL> CREATE TABLE bc_trip
2 (
3 trip_id NUMBER GENERATED ALWAYS AS IDENTITY,
4 bicycle_id NUMBER,
5 trip_start_time DATE,
6 membership_id NUMBER
7 );
Table created.
Procedure:
SQL> CREATE OR REPLACE PROCEDURE begin_trip_sp
2 (p_trip_id OUT INTEGER, -- an output parameter
3 p_bicycle_id IN INTEGER, -- Must not be NULL. Must match value value in BC_BICYCLE and BC_DOCK tables.
4 p_start_time IN DATE, -- If NULL, use CURRENT_DATE system date value
5 p_membership_id IN INTEGER -- Must not be NULL. Must match value in BC_MEMBERSHIP table.
6 )
7 IS
8 BEGIN
9 IF p_bicycle_id IS NULL
10 THEN
11 raise_application_error (
12 -20000,
13 'Missing mandatory value for bicycle id in BEGIN_TRIP_SP. No trip added.');
14 ELSE
15 INSERT INTO bc_trip (bicycle_id, trip_start_time, membership_id)
16 VALUES (p_bicycle_id,
17 NVL (p_start_time, CURRENT_DATE),
18 p_membership_id)
19 RETURNING trip_id
20 INTO p_trip_id;
21 END IF;
22 END;
23 /
Procedure created.
Testing:
SQL> SET SERVEROUTPUT ON
SQL> DECLARE
2 l_trip_id NUMBER;
3 BEGIN
4 begin_trip_sp (l_trip_id,
5 1,
6 NULL,
7 100);
8 DBMS_OUTPUT.put_line ('Inserted trip ID = ' || l_trip_id);
9 END;
10 /
Inserted trip ID = 1
PL/SQL procedure successfully completed.
What if there's no P_BICYCLE_ID?
SQL> l5
5* 1,
SQL> c/1/null/
5* null,
SQL> /
DECLARE
*
ERROR at line 1:
ORA-20000: Missing mandatory value for bicycle id in BEGIN_TRIP_SP. No trip
added.
ORA-06512: at "DP_4005.BEGIN_TRIP_SP", line 11
ORA-06512: at line 4
SQL>
Result is
SQL> SELECT * FROM bc_trip;
TRIP_ID BICYCLE_ID TRIP_START MEMBERSHIP_ID
---------- ---------- ---------- -------------
1 1 08.03.2022 100
SQL>
Just write default value for that parameter:
create or replace procedure BEGIN_TRIP_SP (
p_trip_id OUT INTEGER,
p_bicycle_id IN INTEGER,
p_start_time IN DATE DEFAULT sysdate,--if the parameter is not given it will take sysdate (current date)
p_membership_id IN INTEGER
)...
As a disclaimer, this question is only for my curiosity and for practicing triggers and compound triggers particularly.
I've been trying to replace the UNIQUE constraint with a trigger in order to understand triggers more, but I haven't been successful so far, mainly because of the global variables that I'm not so comfortable with inside the compound triggers.
what I'm trying to do with a trigger :
ALTER TABLE Employee
ADD CONSTRAINT emp_UQ
UNIQUE (id_emp, id_office);
here's what I tried so far (t for type, g for global):
CREATE OR REPLACE TRIGGER BIUUniqueConstraint
FOR INSERT OR UPDATE ON Employee
COMPOUND TRIGGER
TYPE tIdEmpOffice IS TABLE OF Employee.id_emp%TYPE
INDEX BY VARCHAR2(80);
gIdEmpOffice tIdEmpOffice;
TYPE tId_emp IS TABLE OF Employee.id_emp%TYPE;
gId_emp tId_emp;
TYPE tId_office IS TABLE OF Employee.id_office%TYPE;
gId_office tId_office;
BEFORE STATEMENT IS
BEGIN
SELECT e.id_emp, e.id_office
BULK COLLECT INTO gId_emp, gId_office
FROM Employee e
ORDER BY e.id_emp;
FOR j IN 1..gId_emp.COUNT() LOOP
gIdEmpOffice(gId_emp(j)) := gId_office(j);
END LOOP;
END BEFORE STATEMENT;
BEFORE EACH ROW IS
BEGIN
IF INSERTING THEN
DBMS_OUTPUT.PUT_LINE(gIdEmpOffice(:NEW.id_emp);
END IF;
END BEFORE EACH ROW;
END BIUCompteParti;
/
I have no clue how to move forward with this trigger and would like suggestions and explanations if possible about how to use globale variables to store data and how to use them on a row level.
Here's one option.
Sample table:
SQL> create table employee (id_emp number, id_office number);
Table created.
Trigger:
SQL> create or replace trigger trg_emp_unique
2 for insert or update on employee
3 compound trigger
4 type t_row is record (id_emp number, id_office number);
5 type t_tab is table of t_row;
6 l_tab t_tab := t_tab();
7 l_cnt number;
8
9 before each row is
10 begin
11 l_tab.extend;
12 l_tab(l_tab.last).id_emp := :new.id_emp;
13 l_tab(l_tab.last).id_office := :new.id_office;
14 end before each row;
15
16 after statement is
17 begin
18 for i in l_tab.first .. l_tab.last loop
19 select count(*) into l_cnt
20 from employee
21 where id_emp = l_tab(i).id_emp
22 and id_office = l_tab(i).id_office;
23 if l_cnt > 1 then
24 raise_application_error(-20000, 'Unique constraint violated');
25 end if;
26 end loop;
27 l_tab.delete;
28 end after statement;
29 end trg_emp_unique;
30 /
Trigger created.
SQL>
As you can see, it doesn't contain before statement nor after each row parts; if they aren't used, you don't have to put them into the trigger.
Let's try it:
SQL> insert into employee(id_emp, id_office) values (1, 1);
1 row created.
SQL> insert into employee(id_emp, id_office) values (1, 2);
1 row created.
Re-insert the first combination (1, 1):
SQL> insert into employee(id_emp, id_office) values (1, 1);
insert into employee(id_emp, id_office) values (1, 1)
*
ERROR at line 1:
ORA-20000: Unique constraint violated
ORA-06512: at "SCOTT.TRG_EMP_UNIQUE", line 22
ORA-04088: error during execution of trigger 'SCOTT.TRG_EMP_UNIQUE'
That failed (as it should). Let's update existing rows:
SQL> select * from employee;
ID_EMP ID_OFFICE
---------- ----------
1 1
1 2
SQL> update employee set id_office = 5;
update employee set id_office = 5
*
ERROR at line 1:
ORA-20000: Unique constraint violated
ORA-06512: at "SCOTT.TRG_EMP_UNIQUE", line 22
ORA-04088: error during execution of trigger 'SCOTT.TRG_EMP_UNIQUE'
Kind of works.
You can count whether a paired value for those columns already exists with a statement level trigger such as
CREATE OR REPLACE TRIGGER trg_chk_unique_emp_dept_id
AFTER INSERT ON employee
DECLARE
val INT;
BEGIN
SELECT NVL(MAX(COUNT(*)),0)
INTO val
FROM employee
GROUP BY id_emp, id_office;
IF val > 1 THEN
RAISE_APPLICATION_ERROR(-20304,
'Each employee may be assigned to a department once at most !');
END IF;
END;
/
which will check if an attempt made to insert the same value pairs more than once, and if so, it will hurl with an error message.
Demo
Table:
create table department(deptno number, deptname varchar2(50), deptloc varchar2(50));
insert into department values(1,'A','X');
insert into department values(2,'B','Y');
insert into department values(3,'C','Z');
Stored procedure :
create or replace procedure secure_dml(i_month IN varchar2)
is
begin
if i_month <> 'March' then
dbms_output.put_line('You can modify or add a department only at the end of a financial year');
else
--should I write insert/update DML statement?
end;
Trigger :
create or replace trigger tr_check_dept before insert on department
begin
dbms_output.put_line('Record inserted');
end;
Requirement :
Implement the following business rule with the help of a Procedure and a Trigger :-
i. Changes to the data in the Department table, will be allowed only in the month of March.
ii. Create a procedure named SECURE_DML that prevents the DML statement from executing in any other month other than March. In case, if a user tries to modify the table in any other month apart from March, the procedure should display a message
“You can modify or add a department only at the end of a financial year”
iii. Create a statement level trigger named TR_CHECK_DEPT on the Department table that calls the above procedure.
iv. Test it by inserting a new record in the Department table
Basically, you could do everything within a trigger, but OK - that's some kind of a homework. Here's how I understood it.
Procedure doesn't do anything "smart", just displays the message. Note that DBMS_OUTPUT.PUT_LINE call displays a message, it doesn't prevent anyone to do anything - instead of it, you should raise_application_error.
The answer to your question
should I write insert/update DML statement?
is - in my opinion - NO, you shouldn't.
Trigger calls that procedure. Yours doesn't check the month, while it should (i.e. move that control from procedure to trigger).
Everything put together might look like this:
SQL> create or replace procedure secure_dml
2 is
3 begin
4 raise_application_error(-20000,
5 'You can modify or add a department only at the end of a financial year');
6 end;
7 /
Procedure created.
SQL> create or replace trigger tr_check_dept
2 before insert on department
3 for each row
4 begin
5 if extract(month from sysdate) <> 3 then
6 secure_dml;
7 end if;
8 end;
9 /
Trigger created.
SQL> insert into department(deptno, deptname, deptloc)
2 values (4, 'D', 'W');
insert into department(deptno, deptname, deptloc)
*
ERROR at line 1:
ORA-20000: You can modify or add a department only at the end of a financial year
ORA-06512: at "SCOTT.SECURE_DML", line 4
ORA-06512: at "SCOTT.TR_CHECK_DEPT", line 3
ORA-04088: error during execution of trigger 'SCOTT.TR_CHECK_DEPT'
SQL>
Just for testing purposes, as it is September today, let's modify trigger code so that it works for this month (instead of March) and see what INSERT does in that case.
SQL> create or replace trigger tr_check_dept
2 before insert on department
3 for each row
4 begin
5 if extract(month from sysdate) <> 9 then --> this line was changed
6 secure_dml;
7 end if;
8 end;
9 /
Trigger created.
SQL> insert into department(deptno, deptname, deptloc)
2 values (4, 'D', 'W');
1 row created.
SQL>
Right; now it works.
Whatever I do to the trigger always returns "0 rows inserted".
It is like it can't find the new values after inserting them.
After adding the exception it return no_data_found and i don't know why.
before insert or update of rent_date, return_date on rent
for each row
declare
pragma AUTONOMOUS_TRANSACTION;
v_rentDate date;
v_returnDate date;
begin
select rent_date
into v_rentDate
from rent
where rent_date = :new.rent_date;
select return_date
into v_returnDate
from rent
where return_date = :new.return_date;
if v_returnDate < v_rentDate then
raise_application_error(-20158, 'Return date must be after the rent date');
else
dbms_output.put_line('TEST');
end if;
exception when no_data_found then raise_application_error(-20157, 'No data found');
end;
/
insert into rent values (82,sysdate-5,101,sysdate,sysdate+5,100);
--0 rows inserted
It appears that you're doing it the wrong way. Here's why:
you are trying to select values from a table you're currently inserting into (or updating existing values)
Oracle complains that it can't do that because the table is mutating
in order to "fix" it, you used pragma autonomous_transaction which isolates trigger code from the main transaction
You shouldn't use that pragma for such a purpose. Lucky you, trigger can be rewritten in a simpler manner, the one that doesn't cause the mutating table error. As you want to compare rent_date and return_date, do it directly. Here's an example (see line #5):
SQL> create table rent
2 (id number,
3 rent_date date,
4 return_date date
5 );
Table created.
SQL> create or replace trigger trg_biu_rent
2 before insert or update on rent
3 for each row
4 begin
5 if :new.return_date < :new.rent_date then
6 raise_application_error (-20158, 'Return date must be after the rent date');
7 end if;
8 end;
9 /
Trigger created.
Testing:
SQL> -- This will fail
SQL> insert into rent (id, rent_date, return_date) values
2 (1, date '2019-05-25', date '2019-04-10');
insert into rent (id, rent_date, return_date) values
*
ERROR at line 1:
ORA-20158: Return date must be after the rent date
ORA-06512: at "SCOTT.TRG_BIU_RENT", line 3
ORA-04088: error during execution of trigger 'SCOTT.TRG_BIU_RENT'
SQL> -- This is OK
SQL> insert into rent (id, rent_date, return_date) values
2 (1, date '2019-03-28', date '2019-10-20');
1 row created.
SQL>
I'm having trouble in getting started with PL/SQL
Here is my code:
SET SERVEROUTPUT ON;
DECLARE
v_cname customers.customer_name%type := '&customer_name';
v_cardno customers.card_number%type := '&card_number';
v_lastcid customers.customer_id%type;
BEGIN
SELECT customer_id INTO v_lastcid from customers
where customer_id = (select max(customer_id) from customers);
dbms_output.put_line(v_lastcid);
INSERT INTO customers(customer_id, customer_name, card_number)
VALUES(v_lastcid.NEXTVAL, v_cname, v_cardno);
COMMIT;
END;
This returns an error:
ORA-06550: line 12, column 20:
PLS-00487: Invalid reference to variable 'V_LASTCID'
ORA-06550: line 12, column 20:
PL/SQL: ORA-02289: sequence does not exist
ORA-06550: line 11, column 13:
PL/SQL: SQL Statement ignored
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
Any help would me much appreciated!
You probably meant to say MAX + 1 (see lines 7 and 12), i.e.
SQL> create table customers
2 (customer_id number,
3 customer_name varchar2(20),
4 card_number varchar2(20));
Table created.
SQL> DECLARE
2 v_cname customers.customer_name%type := '&customer_name';
3 v_cardno customers.card_number%type := '&card_number';
4 v_lastcid customers.customer_id%type;
5
6 BEGIN
7 SELECT nvl(max(customer_id), 0) INTO v_lastcid from customers
8 where customer_id = (select max(customer_id) from customers);
9 dbms_output.put_line(v_lastcid);
10
11 INSERT INTO customers(customer_id, customer_name, card_number)
12 VALUES(v_lastcid + 1, v_cname, v_cardno);
13 COMMIT;
14 END;
15 /
Enter value for customer_name: Little
Enter value for card_number: Foot
0
PL/SQL procedure successfully completed.
SQL> select * from customers;
CUSTOMER_ID CUSTOMER_NAME CARD_NUMBER
----------- -------------------- --------------------
1 Little Foot
SQL>
Although it works, it is doomed to fail in a multi-user environment if two (or more) users fetch the same MAX value; insert would fail with a DUP-VAL-ON-INDEX error (if the ID is supposed to be unique).
Therefore, use a sequence (which is what NEXTVAL in your code suggests):
SQL> create sequence seq_cust;
Sequence created.
SQL> DECLARE
2 v_cname customers.customer_name%type := '&customer_name';
3 v_cardno customers.card_number%type := '&card_number';
4 v_lastcid customers.customer_id%type;
5 BEGIN
6 INSERT INTO customers(customer_id, customer_name, card_number)
7 VALUES(seq_cust.nextval, v_cname, v_cardno);
8 COMMIT;
9 END;
10 /
Enter value for customer_name: Big
Enter value for card_number: Foot
PL/SQL procedure successfully completed.
nextval is used to get the next value from a sequence. It's usually used to create a pseudo primary key. You can think of it like a special function that can only be called on sequences.
v_lastcid is a variable that has the same type as the customer_id column in the customers table. So if you had a table like this...
CREATE TABLE CUSTOMERS ( CUSTOMER_ID INTEGER );
...then v_lastcid is an integer;
If you're trying to make a dummy customer with the next highest number maybe you mean something like...
INSERT INTO customers(customer_id, customer_name, card_number)
VALUES(v_lastcid + 1, v_cname, v_cardno);
Hope this helps.