Mutating Trigger - SQL - sql

Another one of those mutating trigger errors without an idea of how to fix it. The function counts the number of reviews given and the trigger displays the number of review records:
CREATE OR REPLACE FUNCTION func_no_of_reviews_for_venue (in_venue_id reviews.venue_id%TYPE)
RETURN NUMBER IS
vn_no_of_reviews reviews.review_id%TYPE := 0;
BEGIN
SELECT COUNT(*)
INTO vn_no_of_reviews
FROM reviews;
RETURN vn_no_of_reviews;
END func_no_of_reviews_for_venue;
/
SHOW ERRORS;
-- trigger to get a venue's average rating when a new review has been added
CREATE OR REPLACE TRIGGER trig_get_venue_avg_rating
AFTER INSERT OR UPDATE OF rating ON reviews
FOR EACH ROW
WHEN (NEW.rating IS NOT NULL)
DECLARE
vn_no_reviews NUMBER(6);
vn_avg NUMBER(2,1);
BEGIN
vn_no_reviews := func_no_of_reviews_for_venue(:NEW.venue_id);
DBMS_OUTPUT.PUT_LINE ('Number of reviews: ' || vn_no_reviews);
END trig_age_ck;
/

Related

How to include a SUBSELECT in VALUES of INSERT to take values from different row?

I want to make a trigger that will insert a value from a connected row. For example I have a table with 3 rows as below:
I create a trigger that will work once row 3 and 4 are deleted (in this case will be deleted at the same time). And I want to record invnr and extinvnr from row 1 based on idparent=id. I cannot seem to make it work though.
CREATE OR REPLACE TRIGGER LOG_DELETEDPAYMENTS
BEFORE DELETE ON payments
FOR EACH ROW
BEGIN
IF :old.invnr IS NULL THEN
INSERT INTO TABLE_LOG_DELETEDPAYMENTS (table_name, invnr, extinvnr, invdate, transactionid, info, createdby, deleted_by, date_of_delete)
values ('payments', :old.invnr, :old.extinvnr, :old.invdate, :old:transactionid, :old.info, :old.createdby, sys_context('userenv','OS_USER'), SYSDATE);
END IF;
END;
How can I incorporate this into the trigger above?
Try it this way:
create or replace TRIGGER LOG_DELETEDPAYMENTS
BEFORE DELETE ON payments
FOR EACH ROW
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
Declare
my_invnr PAYMENTS.INVNR%TYPE;
my_extinvnr PAYMENTS.EXTINVNR%TYPE;
Begin
IF :old.INVNR IS NULL THEN
Select INVNR, EXTINVNR
Into my_invnr, my_extinvnr
From PAYMENTS
Where ID = :old.IDPARENT;
--
INSERT INTO TABLE_LOG_DELETEDPAYMENTS (table_name, invnr, extinvnr, invdate, transactionid, info, createdby, deleted_by, date_of_delete)
values ('payments', my_invnr, my_extinvnr, :old.invdate, :old:transactionid, :old.info, :old.createdby, sys_context('userenv','OS_USER'), SYSDATE);
END IF;
End;
END;
You should select the values of INVNR and EXTINVNR based on ID - IDPARENT relationship and store it in the variables (my_invnr and my_extinvnr).
Those variables are used in INSERT into the log statement.
Because of the Select ... Into statement that is reading the affected table - trigger would fail with table PAYMENTS is mutating error.
To avoid that (to separate transaction from the table) you should Declare the PRAGMA AUTONOMOUS_TRANSACTION.
There will be two rows inserted into LOG as the trigger runs FOR EACH (deleted) ROW.
Regards...
This assumes your are on release 12c or greater of Oracle database.
CREATE OR REPLACE PACKAGE LOG_DELETEDPAYMENTS_PKG
AS
-- need a locally defined type for use in trigger
TYPE t_payments_tbl IS TABLE OF payments%ROWTYPE INDEX BY PLS_INTEGER;
END LOG_DELETEDPAYMENTS_PKG;
CREATE OR REPLACE PACKAGE BODY LOG_DELETEDPAYMENTS_PKG
AS
BEGIN
-- could also put the trigger code here and pass the type as a parameter to a procedure
NULL;
END LOG_DELETEDPAYMENTS_PKG;
CREATE OR REPLACE TRIGGER LOG_DELETEDPAYMENTS_CT
FOR DELETE ON payments
COMPOUND TRIGGER
l_tab LOG_DELETEDPAYMENTS_PKG.t_payments_tbl;
l_count PLS_INTEGER:= 0;
BEFORE EACH ROW IS
BEGIN
-- capture the deletes in local type
l_count := l_count + 1;
l_tab(l_count).invnr := :old.invnr;
l_tab(l_count).extinvnr := :old.extinvnr;
l_tab(l_count).invdate := :old.invdate;
l_tab(l_count).transactionid := :old.transactionid;
l_tab(l_count).info := :old.info;
l_tab(l_count).createdby := :old.createdby;
l_tab(l_count).idparent := :old.idparent;
l_tab(l_count).id := :old.id;
END BEFORE EACH ROW;
AFTER STATEMENT IS
BEGIN
FOR i IN l_tab.first .. l_tab.COUNT LOOP
IF(l_tab(i).invnr IS NULL) THEN
-- if the invoice number is NULL, then get info from parent
SELECT p.invnr
,p.extinvnr
INTO l_tab(i).invnr
,l_tab(i).extinvnr
FROM TABLE(l_tab) p
WHERE p.id = l_tab(i).idparent;
END IF;
END LOOP;
-- log all deletes
FORALL i IN 1 .. l_tab.COUNT
INSERT INTO LOG_DELETEDPAYMENTS
(table_name, invnr, extinvnr, invdate, transactionid, info, createdby, deleted_by, date_of_delete)
VALUES
('payments', l_tab(i).invnr, l_tab(i).extinvnr, l_tab(i).invdate, l_tab(i).transactionid, l_tab(i).info, l_tab(i).createdby, sys_context('userenv','OS_USER'), SYSDATE);
l_tab.delete;
END AFTER STATEMENT;
END LOG_DELETEDPAYMENTS_CT;

Trigger to count insert and updates from users

I'm new with oracle sql. I am trying to make a trigger that counts when X user performs an update or an insert, but the TRANSACTIONCONTROL table shows it like this:
DATE--------- USER-----------INSERT----UPDATE
10/03/2022 UserParcial 1 0
10/03/2022 UserParcial 0 1
10/03/2022 UserParcial 1 0
But I want it to look like this:
DATE--------- USER-----------INSERT----UPDATE
10/03/2022 UserParcial 2 1
This is my trigger:
create or replace NONEDITIONABLE TRIGGER TRANSACTIONCONTROL_Trig
AFTER INSERT OR DELETE OR UPDATE on products
for each row
DECLARE
dataTran date;
userTran varchar(30);
InsertTran number:=0;
UpdateTran number:=0;
BEGIN
SELECT SYSDATE INTO dateTran FROM DUAL;
SELECT USER INTO userTran FROM DUAL;
IF INSERTING THEN
InsertTran := InsertTran +1;
INSERT INTO TransactionControl(date, user, insert, updates)
VALUES(dateTran, userTran, insertTran, updateTran);
END IF;
IF UPDATING THEN
updateTran:= updateTran+1;
INSERT INTO TransactionControl(date, user, insert, updates)
VALUES(dateTran, userTran, insertTran, updateTran);
END IF;
END;
If you don't need exact numbers, than mining ALL_TAB_MODIFICATIONS periodically could probably suffice. (I'm curious as to what business function having the count provides)
But if you really must use a trigger, then a compound trigger lets you keep counts at row level, but then summarise at statement level.
Some pseudo code below
create or replace trigger mytrig
for insert or update on mytable
compound trigger
ins_cnt int;
upd_cnt int;
before statement is
begin
ins_cnt := 0;
upd_cnt := 0;
end before statement;
after each row is
begin
if inserting then ins_cnt := ins_cnt + 1; end if;
if updating then upd_cnt := upd_cnt + 1; end if;
end after each row;
after statement is
begin
insert into txn_control ( ... ) values (ins_cnt, upd_cnt);
end after statement;
end;
/

sql insert procedure: insert values into table and control if value is existing in another table

I have little problem. I am trying to insert value into table. This is working. But I would like to control if value id_trainer is existing in another table. I want this -> execute insertClub(1, 5, 'someName'); -> and if id_trainer 5 not exists in table Trainer, procedure gives me message about this. (I tried to translate it into eng. lng., so you can find some gramm. mistakes)
create or replace procedure insertClub
(id_club in number, id_trainer in number, clubName in varchar2)
is
begin
declare counter number;
select count(*) into counter from trianer tr where tr.id_trainer = id_trainer;
if counter = 0 then
DBMS_OUTPUT.PUT_LINE('Trainer with this ID not exists');
end if;
insert into club values(id_club, id_trainer, clubName);
exception
when dup_val_on_index then
DBMS_OUTPUT.PUT_LINE('Dup ID');
end;
/
There is some error in the procedure. Please run below code to create procedure:
create or replace procedure insertClub
(id_club in number, id_trainer in number, clubName in varchar2)
is
counter number;
begin
select count(*) into counter from trianer tr where tr.id_trainer = id_trainer;
if counter = 0 then
DBMS_OUTPUT.PUT_LINE('Trainer with this ID not exists');
end if;
insert into club values(id_club, id_trainer, clubName);
exception
when dup_val_on_index then
DBMS_OUTPUT.PUT_LINE('Dup ID');
end;
/

insert trigger in pl/sql

I cant find the right structure for the following question in pl/sql:
need a trigger on “products table” that will check the price before inserting a new product, product price must not exceed 4000$.
CREATE or REPLACE TRIGGER pro
BEFORE UPDATE OF price
ON products
FOR EACH ROW
declare
pr products.price%type;
BEGIN
if pr < 4000 then
INSERT INTO products VALUES (:old.product_ID,:old.price);
end if;
END;
Please help
Use check constraint instead of trigger:
create table products (price number);
ALTER TABLE PRODUCTS ADD CONSTRAINT check_price CHECK (price < 4000);
Test:
insert into products values (5000) => ERROR
Edit: If you insist on trigger version:
CREATE or REPLACE TRIGGER pro BEFORE insert or UPDATE OF price ON products
FOR EACH ROW
BEGIN
if :new.price > 4000 then
raise_application_error(-20101, 'Price exceeds 4000.');
end if;
END;
The only reason you would bother with a trigger for this instead of a check constraint is to control the error message. And remember that a trigger assumes that the operation is happening, so to stop the operation your tool is to raise an exception.
CREATE OR REPLACE TRIGGER pro
BEFORE INSERT OR UPDATE
ON products
FOR EACH ROW
BEGIN
if :new.price > 4000 then
RAISE_APPLICATION_ERROR(-20001,'Price exceeds maximum permitted value') ;
end if;
END;
if you don't like to raise errors and just keep old values as I think so, use this code:
CREATE or REPLACE TRIGGER pro
BEFORE UPDATE OF price
ON products
FOR EACH ROW
declare
pr products.price%type;
BEGIN
if :new.price > 4000 then
:new.price := :old.price;
:new.product_ID := :old.product_ID;
end if;
END;

PL/SQL Triggers

I am trying to make a Library Information System. I have a table called Borrower(borrower_id: number, name: varchar2(30), status: varchar2(20)). 'status' can be either 'student' or 'faculty'.
I have a restriction that a maximum of 2 books can be issued to a student at any point of time, and 3 to a faculty. How do I implement it using triggers?
This is a homework question. But I've tried hard to come up with some logic. I am new to SQL so this might be easy for you lot but not for me.
I am new to stackexchange, so sorry if I've violated some rules/practices.
I expect that you would maintain a count on the borrower table of the number of books borrowed, and modify it via a trigger when a book is borrowed and when it is returned. Presumably you also have a table for the books being borrowed by the user, and the trigger would be placed on that take.
A constraint on the books_borrowed column could raise an error if the count of borrowed books exceeds 2.
This is a pretty old question, but I found it very useful as I'm also a PL/SQL beginner. There are two approaches to solve the problem and the one you want to use depends on the Oracle DB version.
For older version use a combination of a trigger and a package as below.
CREATE OR REPLACE TRIGGER trg_borrower
BEFORE INSERT OR UPDATE ON borrower
FOR EACH ROW
DECLARE
v_count NUMBER := 0;
BEGIN
v_count := borrower_pkg.count_rows(:NEW.borrower_id, :NEW.name, :NEW.status);
IF :NEW.status = 'student' AND v_count = 2 THEN
RAISE_APPLICATION_ERROR(-20000, 'Error - student');
ELSIF :NEW.status = 'faculty' AND v_count = 3 THEN
RAISE_APPLICATION_ERROR(-20001, 'Error - faculty');
END IF;
END;
/
CREATE OR REPLACE PACKAGE borrower_pkg AS
FUNCTION count_rows(p_id IN borrower.borrower_id%TYPE,
p_name IN borrower.NAME%TYPE,
p_status IN borrower.status%TYPE) RETURN NUMBER;
END;
/
CREATE OR REPLACE PACKAGE BODY borrower_pkg AS
FUNCTION count_rows(p_id IN borrower.borrower_id%TYPE,
p_name IN borrower.NAME%TYPE,
p_status IN borrower.status%TYPE) RETURN NUMBER AS
v_count NUMBER := 0;
BEGIN
SELECT COUNT(*) INTO v_count
FROM borrower
WHERE borrower_id = p_id AND NAME = p_name AND status = p_status;
RETURN v_count;
END count_rows;
END borrower_pkg;
/
For Oracle 10g and above you can use a compound trigger.
CREATE OR REPLACE TRIGGER trg_borrower_comp
FOR INSERT OR UPDATE ON borrower
COMPOUND TRIGGER
CURSOR c_borrower IS
SELECT b1.borrower_id
FROM borrower b1
WHERE EXISTS (SELECT 'x'
FROM borrower b2
WHERE b2.status = 'student' AND b1.borrower_id = b2.borrower_id
GROUP BY borrower_id HAVING COUNT(*) = 2)
OR
EXISTS (SELECT 'x'
FROM borrower b3
WHERE status = 'faculty'AND b1.borrower_id = b3.borrower_id
GROUP BY borrower_id HAVING COUNT(*) = 3);
TYPE t_borrower_count IS TABLE OF borrower.borrower_id%type;
v_borrower_count t_borrower_count;
BEFORE STATEMENT IS
BEGIN
OPEN c_borrower;
FETCH c_borrower BULK COLLECT INTO v_borrower_count;
CLOSE c_borrower;
END BEFORE STATEMENT;
BEFORE EACH ROW IS
BEGIN
IF :NEW.borrower_id MEMBER OF v_borrower_count THEN
RAISE_APPLICATION_ERROR(-20000, 'Error - ' || :NEW.status);
END IF;
END BEFORE EACH ROW;
END;