PL/SQL procedure to insert id to rows having same information - sql

I have contact details data in Oracle table something like below...
I want to insert new column to assign same ID to the contacts having matching information , i.e. based on group of lastname, firstname and (phone and/or email)
Output should look like below
I'm new to this forum, so having formatting issues while posting question, please see attached images for easy understanding of my requirement
Looking for PL/SQL procedure to get this done in our huge database

Due to the nature of the data you presented I see no possible way to do it in one SQL or for it to be better tuned.
Script to create the table and populate with your example:
create table TAB_TEST(LAST_NAME VARCHAR2(200),
FIRST_NAME VARCHAR2(200),
PHONE NUMBER,
EMAIL VARCHAR2(200))
/
INSERT INTO TAB_TEST VALUES
('Ho','Kim', 2058371579, 'ABC#GMAIL.COM')
/
INSERT INTO TAB_TEST VALUES
('Ho','Kim', 4479940000, 'ABC#GMAIL.COM')
/
INSERT INTO TAB_TEST VALUES
('Ho','Kim', 7195739945, 'XYZ#HOTMAIL.COM')
/
INSERT INTO TAB_TEST VALUES
('Ho','Kim', 7475393956, 'XYZ#HOTMAIL.COM')
/
INSERT INTO TAB_TEST VALUES
('Ho','Kim', 7475393956, 'XYZ#HOTMAIL.COM')
/
INSERT INTO TAB_TEST VALUES
('Ho','Kim', 7473430336, null)
/
INSERT INTO TAB_TEST VALUES
('Ho','Kim', 7195739945, 'XYZ#HOTMAIL.COM')
/
INSERT INTO TAB_TEST VALUES
('Ho','Kim', 7475393956, '123#HOTMAIL.COM')
/
INSERT INTO TAB_TEST VALUES
('Ho','Kim', null, '123#HOTMAIL.COM')
/
INSERT INTO TAB_TEST VALUES
('Ho','Kim', 1168548666, '456#HOTMAIL.COM')
/
INSERT INTO TAB_TEST VALUES
('Ho','Kim', 1168548666, '456#HOTMAIL.COM')
/
INSERT INTO TAB_TEST VALUES
('Ho','Kim', 1168548666, null)
/
INSERT INTO TAB_TEST VALUES
('Ho','Kim', 7473430336, null)
/
ALTER TABLE TAB_TEST ADD ID NUMBER
/
And the script to update
DECLARE
lncount NUMBER := 1;
-- had to create this record in order to add the rowid
TYPE lttab_test IS RECORD(
ROWID VARCHAR2(200),
last_name VARCHAR2(200),
first_name VARCHAR2(200),
phone NUMBER,
email VARCHAR2(200),
id NUMBER);
TYPE lttype
IS TABLE OF LTTAB_TEST;
larecords LTTYPE;
BEGIN
SELECT ROWID,
last_name,
first_name,
phone,
email,
id
bulk collect INTO larecords
FROM tab_test a;
FOR i IN 1..larecords.count LOOP
IF Larecords(i).id IS NULL THEN
FOR j IN 1..i-1 LOOP
IF Larecords(j).phone = Larecords(i).phone
AND Larecords(i).id IS NULL THEN
Larecords(i).id := Larecords(j).id;
exit;
END IF;
END LOOP;
FOR j IN 1..i-1 LOOP
IF Larecords(j).email = Larecords(i).email
AND Larecords(i).id IS NULL THEN
Larecords(i).id := Larecords(j).id;
exit;
END IF;
END LOOP;
IF Larecords(i).id IS NULL THEN
Larecords(i).id := lncount;
lncount := lncount + 1;
END IF;
END IF;
END LOOP;
forall i IN 1..larecords.count
UPDATE tab_test
SET id = Larecords(i).id
WHERE ROWID = Larecords(i).ROWID;
COMMIT:
END;

It isn't the most efficient / pretty code but it works in case you just want sequential numbers
DECLARE
I NUMBER := 0;
BEGIN
ALTER TABLE TABLE
ADD ID NUMBER;
COMMIT;
FOR REC IN (SELECT * FROM TABLE)
LOOP
IF(REC.ID IS NULL) THEN
UPDATE TABLE T SET ID = I
WHERE (T.EMAIL = REC.EMAIL OR T.PHONE = REC.PHONE)
AND ID IS NULL;
COMMIT;
I := I + 1;
END IF;
END LOOP;
END;

Related

Using SQL object type in procedure and use them in pipeline function

Here i am trying to use a Pipeline function which will take a Collection as input and return a collection after some validation which i use to insert data into a table;
Here are some test objects which i have created to explain my problem.
create table tst_cri_sdb (icri number, datesitu date, curr varchar2(3), ctmstm varchar2(10));
insert into TST_CRI_SDB values (100, to_date('13032019','ddmmyyyy'), 'EUR', 'STM');
insert into TST_CRI_SDB values (101, to_date('14032019','ddmmyyyy'), 'GBP', 'CTM');
insert into TST_CRI_SDB values (102, to_date('15032019','ddmmyyyy'), 'USD', 'STM');
insert into TST_CRI_SDB values (103, to_date('16032019','ddmmyyyy'), 'INR', 'CTM');
insert into TST_CRI_SDB values (104, to_date('17032019','ddmmyyyy'), 'EUR', 'STM');
create type tst_rec as object (icri number, datesitu date, curr varchar2(3), ctmstm varchar2(10));
create type tst_table_rec as table of tst_rec;
create table sdb_gpcs (curr varchar2(3), ctmstm varchar2(5), goca number, cust_grp varchar2(30));
insert into sdb_gpcs values ('EUR','CTM', 100345 ,'A1105');
insert into sdb_gpcs values ('EUR','CTM', 200345 ,'A4405');
insert into sdb_gpcs values ('EUR','STM', 300345 ,'A3305');
insert into sdb_gpcs values ('USD','CTM', 500345 ,'A5505');
insert into sdb_gpcs values ('USD','STM', 600345 ,'A6605');
insert into sdb_gpcs values ('USD','STM', 700345 ,'A7705');
select * from sdb_gpcs where curr = 'EUR' and ctmstm = 'CTM';
create table tst_cri_plus_sdb (deal_id number, datesitu date, acc_code number, acca_cust_grp varchar2(10), curr varchar2(3), ctmstm varchar2(5));
create type tst_plus_rec as object(deal_id number, datesitu date, acc_code number, acca_cust_grp varchar2(10), curr varchar2(3), ctmstm varchar2(5));
create type tst_plus_table_rec as table of tst_plus_rec;
create or replace function get_plus_sdb_w ( p_tab IN tst_table_rec)
return tst_plus_table_rec PIPELINED
is
l_rec tst_plus_rec;
begin
for i in 1..p_tab.count
loop
for j in (select * from sdb_gpcs)
loop
l_rec := tst_plus_rec(p_tab(i).icri, p_tab(i).datesitu, j.goca, j.cust_grp ,p_tab(i).curr, p_tab(i).ctmstm);
PIPE row(l_rec);
end loop;
end loop;
end;
CREATE or replace procedure tst_insert
is
cursor c1 is select * from tst_cri_sdb;
l_tab tst_table_rec := tst_table_rec();
l_tab_plus tst_plus_table_rec := tst_plus_table_rec();
begin
for i in c1j
loop
l_tab.extend;
l_tab(l_tab.last) := tst_table_rec(tst_rec(i.icri, i.datesitu, i.curr,
i.ctmstm));
end loop;
SELECT *
bulk collect into l_tab_plus
FROM TABLE(get_plus_sdb_w(l_tab));
forall idx IN INDICES OF l_tab_plus
insert into tst_cri_plus_sdb values l_tab(idx);
end;
My idea here is to collect all data tst_cri_sdb table into a collection then pass this collection to a pipeline function which will return a collection again so that i can bulk collect it and insert it in the table tst_cri_plus_sdb.
Help me to collect data in the procedure in a collection and also with pipeline function.
Please ask me more information if required.
I am using --
Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 - 64bit Production
Your procedure has a couple of simple errors; the cursor name is c1 but you later refer to it as c1j, and you're trying to assign a whole table object instance as an element of a table, instead of just a record:
l_tab(l_tab.last) := tst_rec(i.icri, i.datesitu, i.curr, i.ctmstm);
The bigger issue is that you're mixing object and native types. When you unnest the collection with the table() clause you get multiple columns back, not a single object type; so you would have to reconstruct your object:
select tst_plus_rec(deal_id, datesitu, acc_code, acca_cust_grp, curr, ctmstm)
bulk collect into l_tab_plus
from table(get_plus_sdb_w(l_tab));
and then the forall insert would have to return to each object attribute:
forall idx IN INDICES OF l_tab_plus
insert into tst_cri_plus_sdb (deal_id, datesitu, acc_code, acca_cust_grp, curr, ctmstm)
values (l_tab_plus(idx).deal_id, l_tab_plus(idx).datesitu, l_tab_plus(idx).acc_code,
l_tab_plus(idx).acca_cust_grp, l_tab_plus(idx).curr, l_tab_plus(idx).ctmstm);
So putting those together:
create or replace procedure tst_insert
is
cursor c1 is select * from tst_cri_sdb;
l_tab tst_table_rec := tst_table_rec();
-- no need to initialise this one as bulk collect will replace it
-- l_tab_plus tst_plus_table_rec := tst_plus_table_rec();
l_tab_plus tst_plus_table_rec;
begin
for i in c1
loop
l_tab.extend;
l_tab(l_tab.last) := tst_rec(i.icri, i.datesitu, i.curr, i.ctmstm);
end loop;
select tst_plus_rec(deal_id, datesitu, acc_code, acca_cust_grp, curr, ctmstm)
bulk collect into l_tab_plus
from table(get_plus_sdb_w(l_tab));
forall idx IN INDICES OF l_tab_plus
insert into tst_cri_plus_sdb (deal_id, datesitu, acc_code, acca_cust_grp, curr, ctmstm)
values (l_tab_plus(idx).deal_id, l_tab_plus(idx).datesitu, l_tab_plus(idx).acc_code,
l_tab_plus(idx).acca_cust_grp, l_tab_plus(idx).curr, l_tab_plus(idx).ctmstm);
end;
/
exec tst_insert;
select * from tst_cri_plus_sdb;
DEAL_ID DATESITU ACC_CODE ACCA_CUST_ CUR CTMST
---------- ---------- ---------- ---------- --- -----
100 2019-03-13 100345 A1105 EUR STM
100 2019-03-13 200345 A4405 EUR STM
100 2019-03-13 300345 A3305 EUR STM
100 2019-03-13 500345 A5505 EUR STM
100 2019-03-13 600345 A6605 EUR STM
100 2019-03-13 700345 A7705 EUR STM
101 2019-03-14 100345 A1105 GBP CTM
101 2019-03-14 200345 A4405 GBP CTM
...
104 2019-03-17 600345 A6605 EUR STM
104 2019-03-17 700345 A7705 EUR STM
30 rows selected.
You don't need the intermediate collection with bulk collect and forall though, you can insert directly:
create or replace procedure tst_insert
is
cursor c1 is select * from tst_cri_sdb;
l_tab tst_table_rec := tst_table_rec();
-- you don't need this variable at all now
-- l_tab_plus tst_plus_table_rec;
begin
for i in c1
loop
l_tab.extend;
l_tab(l_tab.last) := tst_rec(i.icri, i.datesitu, i.curr, i.ctmstm);
end loop;
insert into tst_cri_plus_sdb
select *
from table(get_plus_sdb_w(l_tab));
end;
/
which gets the same result. And you could avoid the populating-loop by changing that to a bulk-collect:
create or replace procedure tst_insert
is
l_tab tst_table_rec;
begin
select tst_rec(icri, datesitu, curr, ctmstm)
bulk collect into l_tab
from tst_cri_sdb;
insert into tst_cri_plus_sdb
select *
from table(get_plus_sdb_w(l_tab));
end;
/
db<>fiddle
In later versions of Oracle you could do this all in a package without any schema-level object types; but as you're on 11g that won't work (the table() clause will throw PLS-00642).

Triggers in PL-SQL

I have two tables:
create table speciality(
major varchar2(30),
total_credits number,
total_students number);
create table students(
id number(5) primary key,
first_name varchar2(20),
last_name varchar2(20),
major varchar2(30),
current_credits number(3));
I want to create a trigger that carries the name UpdateSpeciality that deletes, updates and inserts into speciality table right when the same operation happens on students table.
This is how the speciality table should look after the following:
SQL> INSERT INTO STUDENTS(id, first_name, last_name, major, current_credits) values(10001, 'sam', 'ali', 'computer science', 11);
SQL>INSERT INTO STUDENTS(id, first_name, last_name, major, current_credits) values(10002, 'kevin', 'mark', 'MIS', 4);
SQL>INSERT INTO STUDENTS(id, first_name, last_name, major, current_credits) values(10003, 'robert', 'jack', 'computer science', 8);
How can I solve this? I don't know how to connect the two tables.
Should I use stored procedures?
CREATE OR REPLACE TRIGGER UpdateSpeciality
after insert or delete or update on students
for each row
begin
if inserting /* this is how far i got */
Something like this should do it. With some assumtions :)
CREATE OR REPLACE TRIGGER UpdateSpeciality
after insert or delete or update on students
for each row
declare
cursor c_spec(sMajor speciality.major%type) is
select * from speciality
where major = sMajor
for update;
r_spec c_spec%ROWTYPE;
begin
if inserting then
open c_spec(:new.major);
fetch c_spec into r_spec;
if c_spec%FOUND then
update speciality set
total_credits = total_credits + :new.current_credits,
total_students = total_students + 1
where current of c_spec;
else
insert into speciality(major, total_credits, total_students) values (:new.major, :new.current_credits, 1);
end if;
close c_spec;
elsif updating then
open c_spec(:new.major);
fetch c_spec into r_spec;
if c_spec%FOUND then
update speciality set
total_credits = total_credits + :new.current_credits - :old.current_credits
where current of c_spec;
else
insert into speciality(major, total_credits, total_students) values (:new.major, :new.current_credits, 1);
end if;
close c_spec;
elsif deleting then
open c_spec(:old.major);
fetch c_spec into r_spec;
if c_spec%FOUND then
update speciality set
total_credits = total_credits - :old.current_credits ,
total_students = total_students - 1
where current of c_spec;
if r_spec.total_students = 1 then
delete from speciality where major = :old.major;
end if;
end if;
close c_spec;
end if;
end;

how can i access a value that's being updated by a trigger?

here's the value of ACCOUNT_NUMBER that has been generated by a sequence and inserted in ACCOUNTS table by ACCOUNT_NUMBER_TRIG trigger that i need to insert it into the TRANSACTION TABLE by the trigger ACCOUNTS_TRANSCATION_TRIG_1
CREATE OR REPLACE TRIGGER ACCOUNT_NUMBER_TRIG
BEFORE INSERT
ON ACCOUNTS
FOR EACH ROW
WHEN (NEW.ACCOUNT_NUMBER is not null)
DECLARE
V_ACC_NO ACCOUNTS.ACCOUNT_NUMBER%TYPE;
BEGIN
SELECT ACCOUNT_NO_SEQ.nextvaL INTO V_ACC_NO FROM DUAL;
:NEW.ACCOUNT_NUMBER := V_ACC_NO;
END ACCOUNT_NUMBER_TRIG;
------------------------------------------------------------------------------
CREATE OR REPLACE TRIGGER ACCOUNTS_TRANSCATION_TRIG_1 AFTER
INSERT ON ACCOUNTS FOR EACH ROW DECLARE CURSOR ACCOUNTS_CUR IS
SELECT ACCOUNT_NUMBER FROM ACCOUNTS;
DECLARE
TEMP_1 NUMBER(5,0);
BEGIN
SELECT ACCOUNTS.ACCOUNT_NUMBER FROM INSERTED INTO TEMP_1
OPEN ACCOUNTS_CUR;
INSERT
INTO TRANSACTIONS VALUES
(
SYSDATE,
- :NEW.ACCOUNT_NUMBER,
'NEW ACCOUNT',
0
);
CLOSE ACCOUNTS_CUR;
END ACCOUNTS_TRANSCATION_TRIG_1;
CREATE TABLE accounts(
ACCOUNT_NUMBER number,
ACCOUNT_NAME varchar2(20)
);
CREATE SEQUENCE ACCOUNT_NO_SEQ;
CREATE OR REPLACE TRIGGER ACCOUNT_NUMBER_TRIG
BEFORE INSERT
ON ACCOUNTS
FOR EACH ROW
WHEN (NEW.ACCOUNT_NUMBER is not null)
BEGIN
:NEW.ACCOUNT_NUMBER :=ACCOUNT_NO_SEQ.nextvaL;
END ACCOUNT_NUMBER_TRIG;
/
CREATE TABLE transactions(
TR_DATE date,
TR_ACCOUNT_NUMBER number,
TR_TYPE varchar2(20),
TR_somenumber int
);
CREATE OR REPLACE TRIGGER ACCOUNTS_TRANSCATION_TRIG_1 AFTER
INSERT ON ACCOUNTS FOR EACH ROW
BEGIN
INSERT INTO TRANSACTIONS( TR_DATE, TR_ACCOUNT_NUMBER, TR_TYPE, TR_somenumber )
VALUES
(
SYSDATE,
:NEW.ACCOUNT_NUMBER,
'NEW ACCOUNT',
0
);
END ACCOUNTS_TRANSCATION_TRIG_1;
/
INSERT INTO accounts( ACCOUNT_NUMBER, ACCOUNT_NAME ) VALUES (1111,'My Name' );
select * from accounts;
ACCOUNT_NUMBER ACCOUNT_NAME
-------------- --------------------
2 My Name
select * from transactions;
TR_DATE TR_ACCOUNT_NUMBER TR_TYPE TR_SOMENUMBER
---------- ----------------- -------------------- -------------
2017/07/11 2 NEW ACCOUNT 0
You can use CURVAL to get the most recent value returned by NEXTVAL:
CREATE OR REPLACE TRIGGER ACCOUNTS_TRANSCATION_TRIG_1 AFTER
INSERT ON ACCOUNTS FOR EACH ROW DECLARE CURSOR ACCOUNTS_CUR IS
BEGIN
INSERT
INTO TRANSACTIONS VALUES
(
SYSDATE,
- ACCOUNT_NO_SEQ.curval,
'NEW ACCOUNT',
0
);
CLOSE ACCOUNTS_CUR;
END ACCOUNTS_TRANSCATION_TRIG_1;
However in this case there is no need, as it has been used to set the ACOUNT_NUMBER:
INSERT
INTO TRANSACTIONS VALUES
(
SYSDATE,
- :NEW.ACCOUNT_NUMBER,
'NEW ACCOUNT',
0
);
BTW unless you are on an old version of Oracle this should work for first trigger:
CREATE OR REPLACE TRIGGER ACCOUNT_NUMBER_TRIG
BEFORE INSERT
ON ACCOUNTS
FOR EACH ROW
WHEN (NEW.ACCOUNT_NUMBER is not null)
BEGIN
:NEW.ACCOUNT_NUMBER := ACCOUNT_NO_SEQ.nextvaL;
END ACCOUNT_NUMBER_TRIG;
(I suspect the WHEN clause is wrong - should be when is null?)

Create and populate Varray in Oracle SQL

I'm trying to créate a Varray of beans type and populate it, but I'm in a hurry and don't find any usefull example.
arr=[[1,'A'],[2,'B'],[3,'C']]
This is my code:
create table my_table (NUM_OPERACIO NUMBER,TITULS varchar2(3)) ;
insert into my_table values(1,'A');
insert into my_table values(2,'B');
insert into my_table values(3,'C');
create TYPE item IS object( NUM_OPERACIO NUMBER, TITULS varchar2(3));
/
create TYPE arr IS VARRAY(10) OF item;
/
insert into arr values( select NUM_OPERACIO, TITULS from my_table);
FOR i IN 1..3 loop
dbms_output.put_line(arr (i));
END loop;
Help me achive this, please.
Thanks in advance
Oracle Setup:
create table my_table (NUM_OPERACIO NUMBER,TITULS varchar2(3)) ;
insert into my_table values(1,'A');
insert into my_table values(2,'B');
insert into my_table values(3,'C');
CREATE TYPE item IS object( NUM_OPERACIO NUMBER, TITULS varchar2(3));
/
CREATE TYPE item_array IS VARRAY(10) OF item;
/
PL/SQL:
DECLARE
arr item_array;
BEGIN
SELECT item( NUM_OPERACIO,TITULS )
BULK COLLECT INTO arr
FROM my_table;
FOR i IN 1..arr.COUNT loop
dbms_output.put_line(arr(i).NUM_OPERACIO || ', ' || arr(i).TITULS);
END loop;
END;
/

After insert trigger causing mutating table error

Create table statements:
CREATE TABLE Client(
nclient INT NOT NULL,
nom VARCHAR(45) NOT NULL UNIQUE,
plafond NUMERIC(10,2) DEFAULT 0,
CHECK (plafond>=0),
CONSTRAINT PK_Client PRIMARY KEY (nclient)
);
CREATE TABLE Compte(
ncompte INT NOT NULL,
solde NUMERIC(10,2),
ouvert NUMERIC(1) DEFAULT 1,
nbessais INT,
code Numeric(4),
nclient NUMERIC(4),
CONSTRAINT PK_Compte PRIMARY KEY (ncompte),
CONSTRAINT FK_Compte_nclient_Client FOREIGN KEY (nclient) REFERENCES Client(nclient),
CONSTRAINT CK_solde CHECK (solde>=0),
CONSTRAINT CK_ouvert CHECK(ouvert between 0 and 1),
CONSTRAINT CK_code CHECK (code>0)
);
CREATE TABLE Operation(
noperation INT NOT NULL,
dateoperation DATE Default SYSDATE,
codepropose NUMERIC(4),
montant NUMERIC(10,2),
ncompte INT,
CONSTRAINT PK_Operation PRIMARY KEY (noperation),
CONSTRAINT FK_Ope_ncompte_Compte FOREIGN KEY (ncompte) REFERENCES Compte(ncompte),
CONSTRAINT CK_codepropose CHECK (codepropose>=0),
CONSTRAINT CK_montant CHECK (montant>=0)
);
CREATE TABLE Historique(
nhistorique INT NOT NULL,
dateoperation DATE DEFAULT SYSDATE,
montant NUMERIC(10,2),
ncompte INT,
CONSTRAINT PK_Historique PRIMARY KEY (nhistorique)
);
CREATE TABLE Incident(
nincident INT NOT NULL,
message VARCHAR(45),
noperation INT,
CONSTRAINT PK_Incident PRIMARY KEY (nincident),
CONSTRAINT FK_Inc_noperation_Operation FOREIGN KEY (noperation) REFERENCES Operation(noperation)
);
Trigger code:
create or replace TRIGGER TRG_OPERATION_CARTE
AFTER INSERT OR UPDATE ON Operation
FOR EACH ROW
DECLARE
solde NUMBER := 0;
ouvert NUMBER := 0;
essai NUMBER := 0;
code NUMBER := 0;
plafondActuel NUMBER := 0;
decouvertAuth NUMBER := 0;
BEGIN
SELECT SUM(montant) INTO plafondActuel FROM Operation WHERE dateoperation >= SYSDATE - 7 AND ncompte = :NEW.ncompte;
SELECT plafond INTO decouvertAuth FROM Client Cl LEFT JOIN Compte Cp ON Cl.nclient = Cp.nclient WHERE Cp.ncompte = :NEW.ncompte;
SELECT solde, ouvert, nbessais, code
INTO solde, ouvert, essai, code
FROM Compte
WHERE Compte.ncompte = :NEW.ncompte;
IF ouvert = 0 THEN
INSERT INTO Incident (message,noperation) values ('Compte bloqué',:NEW.noperation);
ELSE
IF :NEW.codepropose = code THEN
IF (solde + decouvertAuth) > :NEW.montant THEN
IF (plafondActuel + :NEW.montant) > 300 THEN
INSERT INTO Incident (message,noperation) values ('Lopération dépasse le plafond authorisé',:NEW.noperation);
ELSE
INSERT INTO Historique (montant,ncompte) values (:NEW.montant,:NEW.ncompte);
END IF;
ELSE
INSERT INTO Incident (message,noperation) values ('Lopération dépasse le découvert authorisé',:NEW.noperation);
END IF;
ELSE
essai := essai + 1;
UPDATE Compte SET nbessais = essai WHERE Compte.ncompte = :NEW.ncompte;
IF essai >=3 THEN
UPDATE Compte SET ouvert = 0 WHERE Compte.ncompte = :NEW.ncompte;
INSERT INTO Incident (message,noperation) values ('Compte bloqué, 3 tentatives échouées',:NEW.noperation);
END IF;
END IF;
END IF;
END;
I am pretty sure that the algorithm and the Trigger are correct but I have an error :
Cause: A trigger (or a user defined plsql function that is referenced in this statement) attempted to look at (or modify) a table that was in the middle of being modified by the statement which fired it.
Action: Rewrite the trigger (or function) so it does not read that table.
Here are some tests, everything is ok apart from the last insert :
INSERT INTO Client (nom) values ('abderrahmane');
INSERT INTO Client (nom) values ('ben');
Select * FROM Client;
INSERT INTO Compte (solde,nclient) values (200,1);
INSERT INTO Compte (nclient,code) values (2,2403);
INSERT INTO Compte (solde,nclient) values (2300,3);
SELECT * FROM Compte;
INSERT INTO Operation (montant,ncompte) values (200,2);
INSERT INTO Operation (codepropose,ncompte) values (2010,2);
INSERT INTO Operation (ncompte) values (1);
INSERT INTO Operation (codepropose,montant,ncompte) values (2020,20,1);
INSERT INTO Operation (codepropose,montant,ncompte,dateoperation) values (2030,30,2,'01/01/2001');
INSERT INTO Operation (codepropose,montant,ncompte) values (2030,30,3);
SELECT * FROM Operation;
INSERT INTO Historique (montant,ncompte) values (200,2);
INSERT INTO Historique (dateoperation,ncompte) values ('15/04/16',2);
INSERT INTO Historique (ncompte) values (1);
INSERT INTO Historique (dateoperation,montant,ncompte) values ('15/04/16',20,1);
INSERT INTO Historique (dateoperation,montant,ncompte) values ('15/04/16',30,3);
SELECT * FROM Historique;
INSERT INTO Incident (message,noperation) values ('Incident 1',2);
INSERT INTO Incident (message,noperation) values ('Incident 2',8);
INSERT INTO Incident (message,noperation) values ('Incident 3',1);
SELECT * FROM Incident;
UPDATE Compte SET ouvert = 0 WHERE nclient = 2;
INSERT INTO Operation (montant,ncompte,codepropose) values (200,2,2403);
SELECT * FROM Incident;
Mutating Table error comes because your are using that particular table and making any kind of modification in the same table.
Since you are trying to select records from table in the trigger of the same table you are getting this error.
Thanks everyone for your answers, I just had to change the tablename, instead of taking the sum from operation, I took it from historique :
CREATE OR REPLACE TRIGGER TRG_OPERATION_CARTE
AFTER INSERT OR UPDATE ON Operation
FOR EACH ROW
DECLARE
solde NUMBER := 0;
ouvert NUMBER := 0;
essai NUMBER := 0;
code NUMBER := 0;
plafondActuel NUMBER := 0;
decouvertAuth NUMBER := 0;
BEGIN
SELECT SUM(montant) INTO plafondActuel FROM Historique WHERE dateoperation >= SYSDATE - 7 AND ncompte = :NEW.ncompte;
SELECT plafond INTO decouvertAuth FROM Client Cl LEFT JOIN Compte Cp ON Cl.nclient = Cp.nclient WHERE Cp.ncompte = :NEW.ncompte;
SELECT solde, ouvert, nbessais, code
INTO solde, ouvert, essai, code
FROM Compte
WHERE Compte.ncompte = :NEW.ncompte;
IF ouvert = 0 THEN
INSERT INTO Incident (message,noperation) values ('Compte bloqué',:NEW.noperation);
ELSE
IF :NEW.codepropose = code THEN
IF (solde + decouvertAuth) >= :NEW.montant THEN
IF (plafondActuel + :NEW.montant) > 300 THEN
INSERT INTO Incident (message,noperation) values ('Lopération dépasse le plafond authorisé',:NEW.noperation);
ELSE
UPDATE Compte SET solde = (solde - :NEW.montant) WHERE Compte.ncompte = :NEW.ncompte;
INSERT INTO Historique (montant,ncompte) values (:NEW.montant,:NEW.ncompte);
END IF;
ELSE
INSERT INTO Incident (message,noperation) values ('Lopération dépasse le découvert authorisé',:NEW.noperation);
END IF;
ELSE
essai := essai + 1;
UPDATE Compte SET nbessais = essai WHERE Compte.ncompte = :NEW.ncompte;
IF essai >=3 THEN
UPDATE Compte SET ouvert = 0 WHERE Compte.ncompte = :NEW.ncompte;
INSERT INTO Incident (message,noperation) values (CONCAT('Compte bloqué : tentative n° ', essai),:NEW.noperation);
ELSE
INSERT INTO Incident (message,noperation) values (CONCAT('Code erroné : tentative n° ', essai),:NEW.noperation);
END IF;
END IF;
END IF;
END;
/