Converting Merge clause with Bulk collect/FORALL in pl/sql - sql

I wrote a procedure where the data gets updated/inserted simultaneously to the destination table from source table. The procedure is working fine for less no of records, but when i try to execute more records its taking more time to perform the operation.
Can we convert merge clause with bulk collect where the logic remains same ? i dint find any useful resources.
I have attached my merge procedure .
create or replace PROCEDURE TEST1 (
p_array_size IN NUMBER
) IS
CURSOR dtls IS SELECT DISTINCT
account_num
FROM
table1
WHERE
rprtd_till_dt = (
SELECT
dt - 1
FROM
dates
WHERE
id = 'odc'
);
TYPE data_tbl IS TABLE OF dtls%rowtype;
data data_tbl;
BEGIN
DECLARE
v_noofDays NUMBER:=0;
currentDt DATE;
BEGIN
SELECT dt INTO currentDt FROM dates WHERE id = 'odc';
BEGIN
OPEN dtls;
LOOP
FETCH dtls BULK COLLECT INTO data LIMIT p_array_size;
EXIT WHEN data.COUNT = 0;
FOR i IN 1..data.COUNT
LOOP
IF(TRUNC(data(i).creation_dt,'MM') = TRUNC(currentDt,'MM')) THEN
v_noofDays := currentDt - 1 - data(i).creation_dt;
ELSE
v_noofDays := currentDt - TRUNC(currentDt,'MM');
END IF;
MERGE INTO table1 updtbl USING ( SELECT
d.*
FROM
table2 d,
(
SELECT
b.prdct_id,
FROM
table3 a,
table2 b
WHERE
a.ir_id = b.ir_id
AND a.price_component_id = b.price_component_id
AND a.financial_institution_id = b.financial_institution_id
GROUP BY
b.prdct_id,
) e
WHERE
d.prdct_id = e.prdct_id
AND d.bndng_typ = data(i).bndng_typ
AND d.bndng_val = data(i).bndng_val
AND d.financial_institution_id = data(i).financial_institution_id
AND d.prdct_id = data(i).prdct_id
AND d.prdct_sub_id = data(i).prdct_sub_id
AND d.instrmnt_id = data(i).instrmnt_id
)
inp ON (
updtbl.POS_NUM = data(i).POS_NUM
AND updtbl.POS_TYPE = data(i).POS_TYPE
AND updtbl.PRICE_COMPONENT_ID = inp.PRICE_COMPONENT_ID
AND updtbl.RPRTD_TILL_DT = data(i).RPRTD_TILL_DT
)
WHEN NOT MATCHED THEN
INSERT VALUES (
data(i).loan_account_num,
inp.ir_id,
inp.price_component_id,
)
WHEN MATCHED THEN
update SET SEQ_NUM=1,
NET_INTRST_AMT=round(data(i).curr_loan_bal*inp.price_component_value*v_noofDays/36000,2),
DM_BTID=200
WHERE SEQ_NUM=2;
COMMIT;
END LOOP;
END LOOP;
CLOSE dtls;
END;
END;
END TEST1;
/
If anyone can help me to guide the syntax on how to achieve the above procedure using bulk collect will be helpful.

I know its a bit late, but use the following for future if you haven't solved it yet
drop table projects;
create table projects (
proj_id integer not null primary key,
proj_title varchar2(20)
);
insert into projects (proj_id, proj_title) values (1, 'Project One');
insert into projects (proj_id, proj_title) values (2, 'Project Two');
commit;
select *
from projects;
declare
type varray_t is varray(2) of projects%rowtype;
arr varray_t;
begin
with test_data as (select 2 as proj_id, 'New Project Two' as proj_title from dual
union all select 3 as proj_id, 'New Project Three' as proj_title from dual)
select proj_id, proj_title
bulk collect into arr
from test_data;
forall i in arr.first .. arr.last
merge into projects
using (select arr(i).proj_id as proj_id,
arr(i).proj_title as proj_title
from dual) mrg
on (projects.proj_id = mrg.proj_id)
when matched then update set projects.proj_title = mrg.proj_title
when not matched then insert (proj_id, proj_title) values (mrg.proj_id, mrg.proj_title);
dbms_output.put_line(sql%rowcount || ' rows merged');
commit;
end;

I hope this will give you kind of idea. Avoid the copy and paste and check the syntax.
create or replace PROCEDURE TEST1 (
p_array_size IN NUMBER
) IS
CURSOR dtls IS SELECT DISTINCT
account_num
FROM
table1
WHERE
rprtd_till_dt = (
SELECT
dt - 1
FROM
dates
WHERE
id = 'odc'
);
TYPE data_tbl IS TABLE OF dtls%rowtype;
data data_tbl;
BEGIN
DECLARE
v_noofDays NUMBER:=0;
currentDt DATE;
BEGIN
SELECT dt INTO currentDt FROM dates WHERE id = 'odc';
BEGIN
OPEN dtls;
LOOP
FETCH dtls BULK COLLECT INTO data LIMIT p_array_size;
EXIT WHEN data.COUNT = 0;
FORALL rec in data.first .. data.last
MERGE INTO table1 updtbl USING (
SELECT
d.* FROM
table2 d,(
SELECT
b.prdct_id
FROM
table3 a,
table2 b
WHERE
a.ir_id = b.ir_id
AND a.price_component_id = b.price_component_id
AND a.financial_institution_id = b.financial_institution_id
GROUP BY
b.prdct_id
) e
WHERE
d.prdct_id = e.prdct_id
AND d.bndng_typ = data(rec).bndng_typ
AND d.bndng_val = data(rec).bndng_val
AND d.financial_institution_id = data(rec).financial_institution_id
AND d.prdct_id = data(rec).prdct_id
AND d.prdct_sub_id = data(rec).prdct_sub_id
AND d.instrmnt_id = data(rec).instrmnt_id
)
inp ON (
updtbl.POS_NUM = data(rec).POS_NUM
AND updtbl.POS_TYPE = data(rec).POS_TYPE
AND updtbl.PRICE_COMPONENT_ID = data(rec).PRICE_COMPONENT_ID
AND updtbl.RPRTD_TILL_DT = data(rec).RPRTD_TILL_DT
)
WHEN NOT MATCHED THEN
INSERT VALUES (
data(rec)
)
WHEN MATCHED THEN
update SET SEQ_NUM=1,
NET_INTRST_AMT=round(data(rec).curr_loan_bal*inp.price_component_value*v_noofDays/36000,2),
DM_BTID=200
WHERE SEQ_NUM=2;
END LOOP;
CLOSE dtls;
END;
END;
END TEST1;

Merge is always better than forall for atomic updates.
A simplistic use case is
https://ograycoding.wordpress.com/2012/10/13/oracle-merge-v-bulk-collect-and-forall/

Related

SELECT INTO do not store null in to the variables

I was trying to check the values from the two tables, and I only want to proceed to the next query only if the value is found in TableA. There is something wrong in the below logic, also when there is not data, it goes to exception instead of storing NULL into the variables.
SET SERVEROUTPUT ON;
SET FEEDBACK OFF;
SPOOL temp.txt;
DECLARE
v_ATM TableA.CODE%TYPE := NULL;
v_TBL TableB.CODE%TYPE := NULL;
BEGIN
SELECT TableA.CODE, TableB.CODE INTO v_ATM,v_TBL FROM TableA LEFT JOIN TableB ON TableA.CODE = TableB.CODE WHERE TableA.CODE = 'ABC';
IF (v_ATM IS NULL) AND (v_TBL IS NULL) THEN
DBMS_OUTPUT.PUT_LINE('No value found');
ELSIF (v_ATM IS NOT NULL) AND (v_TBL IS NOT NULL) THEN
DBMS_OUTPUT.PUT_LINE('Found in both');
ELSIF (v_ATM IS NULL) AND (v_TBL IS NOT NULL) THEN
DBMS_OUTPUT.PUT_LINE('Found in Table B');
ELSIF (v_ATM IS NOT NULL) AND (v_TBL IS NULL) THEN
DBMS_OUTPUT.PUT_LINE('Found in Table A');
END IF;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('Exception');
END;
Alternately I was trying:
SELECT TableA.CODE INTO v_ATM FROM TableA WHERE TableA.CODE = 'ABC';
SELECT TableB.CODE INTO v_TBL FROM TableB WHERE TableB.CODE = 'ABC';
but still, v_ATM does not store NULL if ABC is not found, and goes to exception.
--instead of selcting code col to get null/not null, select count(1) to get either 0/1 as output w/o errors
with
TableA as (
--select 'ABC' as code from dual
--union all
select 'DEF' as code from dual
),
TableB as (
--select 'ABC' as code from dual
--union all
select 'DEF' as code from dual
),
q1 as (
SELECT 1 as id, count(1) as cnt FROM TableA WHERE TableA.CODE = 'ABC'
),
q2 as (
SELECT 1 as id, count(1) as cnt FROM TableB WHERE TableB.CODE = 'ABC'
)
select
cast(
case
when q1.cnt = 0 and q2.cnt = 0 then 'Both missing'
when q1.cnt = 1 and q2.cnt = 0 then 'TableA present, TableB missing'
when q1.cnt = 0 and q2.cnt = 1 then 'TableA missing, TableB present'
when q1.cnt = 1 and q2.cnt = 1 then 'Both present'
else 'NA'
end as varchar2(40)) as output_txt
from q1 inner join q2 on (q1.id = q2.id)
;
Play with the sql by hiding ABC and DEF and union all lines of code as desired to get different outputs for different conditions.
You're selecting from a row, not a scalar value so if there is no row there is no value.
If you SELECT MAX(TableA.CODE) ... WHERE ...
You will get a value to use

Oracle optimize select after update status performance

I have a store procedure that will
Update maximum 500 rows status from 0 to 1
Return those rows to program via cursor
Here is my store procedure code
PROCEDURE process_data_out (
o_rt_cursor OUT SYS_REFCURSOR
) IS
v_limit NUMBER;
l_data_ids VARCHAR2(32000);
BEGIN
v_limit := 500; -- limit 500
l_data_ids := '';
-- Create loop to get data
FOR i IN (
SELECT *
FROM
(
SELECT id FROM
TBL_DATA a
WHERE
a.created_at BETWEEN SYSDATE - 0.5 AND SYSDATE + 0.1
AND a.status = 0
AND a.phone NOT IN (SELECT phone FROM TBL_BIG_TABLE_1)
AND a.phone NOT IN (SELECT phone FROM TBL_BIG_TABLE_2 WHERE IS_DENY = 1)
ORDER BY
priority
)
WHERE
ROWNUM <= v_limit
) LOOP
BEGIN
-- Build string of ids like id1,id2,id3,
l_data_ids := l_data_ids
|| i.id
|| ',';
-- update row status to prevent future repeat
UPDATE TBL_DATA
SET
status = 1
WHERE
id = i.id;
END;
END LOOP;
COMMIT;
-- If string of ids length >0 open cursor to take data
IF ( length(l_data_ids) > 0 )
THEN
-- Cut last comma id1,id2,id3, --> id1,id2,id3
l_data_ids := substr(l_data_ids,1,length(l_data_ids) - 1);
-- open cursor
OPEN o_rt_cursor FOR
SELECT
id,
phone
FROM
TBL_DATA a
WHERE
a.id IN (
SELECT
to_number(column_value)
FROM
XMLTABLE ( l_data_ids )
);
END IF;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
END process_data_out;
I want to optimize this performance and here is my question
Should I replace in by exists
Replace
AND a.phone NOT IN (SELECT phone FROM TBL_BIG_TABLE_1)
AND a.phone NOT IN (SELECT phone FROM TBL_BIG_TABLE_2 WHERE IS_DENY = 1)
by
AND NOT Exists (SELECT phone FROM TBL_BIG_TABLE_1 where TBL_BIG_TABLE_1.phone = a.phone)
AND NOT Exists (SELECT phone FROM TBL_BIG_TABLE_2 WHERE TBL_BIG_TABLE_2.phone = a.phone and IS_DENY = 1)
Is there a better way than
Save a string of ids like id1,id2,id3 after update row status
Open cursor by select from string of ids
I appreciate for any suggestion.
Thank for your concern
Row-by-row processing is always slower and you are also creating the string of ids, which again takes time so overall performance is going down.
You can use the collection DBMS_SQL.NUMBER_TABLE to store the updated ids from the UPDATE statement using the RETURNING clause and use it in the cursor query.
Also, I have changed your update statement so that it does not use NOT IN and uses the LEFT JOINS and ROW_NUMBER analytical function for increasing the performance as follows:
CREATE OR REPLACE PROCEDURE PROCESS_DATA_OUT (
O_RT_CURSOR OUT SYS_REFCURSOR
) IS
V_LIMIT NUMBER;
L_DATA_IDS DBMS_SQL.NUMBER_TABLE;
BEGIN
V_LIMIT := 500; -- limit 500
UPDATE TBL_DATA A
SET A.STATUS = 1
WHERE A.ID IN (
SELECT ID
FROM ( SELECT ID,
ROW_NUMBER() OVER(ORDER BY PRIORITY) AS RN
FROM TBL_DATA B
LEFT JOIN TBL_BIG_TABLE_1 T1 ON T1.PHONE = B.PHONE
LEFT JOIN TBL_BIG_TABLE_2 T2 ON T2.IS_DENY = 1 AND T2.PHONE = B.PHONE
WHERE B.CREATED_AT BETWEEN SYSDATE - 0.5 AND SYSDATE + 0.1
AND B.STATUS = 0
AND T1.PHONE IS NULL
AND T2.PHONE IS NULL)
WHERE RN <= V_LIMIT ) RETURNING ID BULK COLLECT INTO L_DATA_IDS;
OPEN O_RT_CURSOR FOR SELECT ID, PHONE
FROM TBL_DATA A
WHERE A.ID IN (SELECT COLUMN_VALUE FROM TABLE ( L_DATA_IDS ));
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
END PROCESS_DATA_OUT;
/

Error(13,34): PLS-00201: identifier 'D.BNDNG_TYP' must be declared

I wrote a procedure where i am trying to insert value from source to destination table, i used bulk collect in order to execute the data for large amount of data.
create or replace PROCEDURE TEST2 (
p_array_size IN NUMBER
) IS
CURSOR cur1 IS SELECT DISTINCT
*
FROM
test;
CURSOR cur3 IS SELECT * FROM test;
CURSOR cur2( BND_TYPE d.bndng_typ%TYPE, BND_VAL d.bndng_val%TYPE, FINANCIAL_INST_ID d.financial_institution_id%TYPE, PRDCT_ID d.prdct_id%TYPE,
PRDCT_SUB_ID d.prdct_sub_id%TYPE, INSTRUMENT_ID d.instrmnt_id%TYPE) IS SELECT
d.*
FROM
test1 d,
(
SELECT
b.prdct_id,
FROM
test2 a,
test1 b
WHERE
a.ir_id = b.ir_id
AND a.price_component_id = b.price_component_id
AND a.financial_institution_id = b.financial_institution_id
GROUP BY
b.prdct_id,
) e
WHERE
d.prdct_id = e.prdct_id
AND d.bndng_typ = BND_TYPE
AND d.bndng_val = BND_VAL
AND d.financial_institution_id = FINANCIAL_INST_ID
AND d.prdct_id = PRDCT_ID
AND d.prdct_sub_id = PRDCT_SUB_ID
AND d.instrmnt_id = INSTRUMENT_ID ;
TYPE loan_data_tbl IS TABLE OF cur1%rowtype INDEX BY PLS_INTEGER;
loan_data loan_data_tbl;
TYPE loanrate_tbl IS TABLE OF cur2%rowtype INDEX BY BINARY_INTEGER;
loan_rate loanrate_tbl;
BEGIN
DECLARE
v_noofDays NUMBER:=0;
currentDt DATE;
BEGIN
SELECT * INTO currentDt FROM dt;
BEGIN
IF cur1%Isopen Then
Close cur1;
End IF;
IF cur2%Isopen Then
Close cur2;
End IF;
OPEN cur1;
LOOP
FETCH cur1 BULK COLLECT INTO loan_data LIMIT p_array_size;
EXIT WHEN loan_data.COUNT = 0;
FOR i IN 1..loan_data.COUNT
LOOP
OPEN cur3;
OPEN cur2(loan_data(i).bndng_typ, loan_data(i).bndng_val,loan_data(i).financial_institution_id,
loan_data(i).prdct_id, loan_data(i).prdct_sub_id, loan_data(i).instrmnt_id);
loop
FETCH cur2 BULK COLLECT INTO loan_rate LIMIT p_array_size;
EXIT WHEN loan_rate.COUNT = 0;
FOR j IN 1..loan_rate.COUNT
LOOP
IF(cur3.POS_NUM = loan_data(i).POS_NUM AND cur3.POS_TYPE = loan_data(i).POS_TYPE
AND cur3.PRICE_COMPONENT_ID = loan_rate(j).PRICE_COMPONENT_ID
AND cur3.RPRTD_TILL_DT = loan_data(i).RPRTD_TILL_DT) THEN
update test SET SEQ_NUM=1,
WHERE SEQ_NUM=2;
ELSE
INSERT INTO test VALUES (
....
....
);
END IF;
COMMIT;
END LOOP;
END LOOP;
CLOSE cur2;
CLOSE cur1;
END LOOP;
END LOOP;
CLOSE cur1;
END;
END;
End ;
/
In above procedure, i removed some column names for security purpose.
I am getting two errors one is
PLS-00201: identifier 'D.BNDNG_TYP' must be declared and
PLS-00225: subprogram or cursor 'cur3' reference is out of scope
if any one can help to solve this.
In cursor, You cannot provide the type referring to alias from cursor query. You need to just provide the table name test1 instead of alias d.
CURSOR cur2( BND_TYPE test1.bndng_typ%TYPE,
BND_VAL test1.bndng_val%TYPE,
FINANCIAL_INST_ID test1.financial_institution_id%TYPE,
PRDCT_ID test1.prdct_id%TYPE,
PRDCT_SUB_ID test1.prdct_sub_id%TYPE,
INSTRUMENT_ID test1.instrmnt_id%TYPE)
IS SELECT
.....
This is untested as I don't have your tables and data, but as a first refactoring I would start with this type of structure:
create or replace procedure test2
as
cursor loan_data_cur
( bnd_type cr_loan_prima_rate_orig.bndng_typ%type
, bnd_val cr_loan_prima_rate_orig.bndng_val%type
, financial_inst_id cr_loan_prima_rate_orig.financial_institution_id%type
, prdct_id cr_loan_prima_rate_orig.prdct_id%type
, prdct_sub_id cr_loan_prima_rate_orig.prdct_sub_id%type
, instrument_id cr_loan_prima_rate_orig.instrmnt_id%type )
is
select d.*
from test1 d
join ( select b.prdct_id
from test2 a
join test1 b
on b.ir_id = a.ir_id
and b.price_component_id = a.price_component_id
and b.financial_institution_id = a.financial_institution_id
group by b.prdct_id ) e
on d.prdct_id = e.prdct_id
where d.bndng_typ = loan_data_cur.bnd_type
and d.bndng_val = loan_data_cur.bnd_val
and d.financial_institution_id = loan_data_cur.financial_inst_id
and d.prdct_id = loan_data_cur.prdct_id
and d.prdct_sub_id = loan_data_cur.prdct_sub_id
and d.instrmnt_id = loan_data_cur.instrument_id;
begin
for loan_data in (
select distinct *
from test
)
loop
for loan_rate in loan_data_cur
( loan_data.bndng_typ
, loan_data.bndng_val
, loan_data.financial_institution_id
, loan_data.prdct_id
, loan_data.prdct_sub_id
, loan_data.instrmnt_id )
loop
update test t set t.seq_num = 1
where t.seq_num = 2
and t.pos_num = loan_data.pos_num
and t.pos_type = loan_data.pos_type
and t.price_component_id = loan_rate.price_component_id
and t.rprtd_till_dt = loan_data.rprtd_till_dt;
if sql%rowcount = 0 then
insert into test values (x, y, z);
end if;
end loop;
end loop;
commit;
end test2;
I would also looking at doing the join from test to test1 as a single cursor instead of explicitly with two separate cursors, as the optimiser might find a more efficient method, such as a hash join.
Probably the update/insert combination could be written as a single merge. Once you have a merge working, you might find you can apply it to the whole table in one shot and not need any cursor loops at all.
Bulk-collecting into an array could be useful if the number of row-by-row updates causes performance problems (if it's more than a few thousand, say). If so, you would want to structure it so that you could apply the updates/inserts using a forall construction, not more loops.

PL/SQL procedure to output line the given date if not existing, latest date should be given

I have this table informationvalues with the contents:
Now I create a procedure where I need to input a date parameter which should output line the correct attr with given price. If the date doesn't exist the latest date should be selected.
The solution table for to_date('01-jan-19') would look like this:
This would be then output line in the procedure.
Should I select to correct tuple and output line it or would it be best to just bulk collect everything and then check in a for loop with an if statement what tuple I need to display.
What I have so far:
A select statement with the tuples I am looking for:
create or replace procedure print_inf_value(closingDate Date) is
cursor d1 (closingDate Date) is
select t.attr, t.dateOfValue, t.price
from (
select i.*,
row_number() over (
partition by attr
order by case when dateOfValue = closingdate then 1 else 2 end, dateOfValue desc
) rn
from InformationValues i
) t
where t.rn = 1;
BEGIN
dbms_output.put_line('Information Value ');
dbms_output.put_line('--------------------------------');
FOR d1_rec IN d1 LOOP
dbms_output.put_line(d1_rec.attr || ' ' || d1_rec.price );
END LOOP;
END;
Or a procedure where I bulk collect everything and then I need to sort out what tuple I need:
create or replace procedure print_inf_value(closingDate Date) is
TYPE d1 IS TABLE OF informationvalues%rowtype;
emps d1;
begin select * bulk collect into emps
from informationvalues;
FOR i IN 1 .. emps.COUNT LOOP
if emps(i).dateofvalue = closingDate then
dbms_output.put_line(emps(i).attr || ' ' || emps(i).price );
/*else*/
end if;
END LOOP;
END;
Both are not working right, so what am I missing to display tuple with the correct date.
Please try:
CREATE OR REPLACE PROCEDURE print_inf_value (closingDate DATE)
IS
BEGIN
DBMS_OUTPUT.put_line (RPAD ('ATTR', 20) || RPAD ('PRICE', 20));
FOR o
IN (select attr, trim(case when price < 1 then to_char(price,90.9) else to_char(price) end) price from (
select attr, price, dateofvalue,
row_number() over (partition by attr order by dateofvalue desc) rn from informationvalues
) i where dateofvalue = closingdate
or (rn = 1 and not exists (select 1 from informationvalues iv where iv.attr = i.attr and dateofvalue = closingdate) )
)
LOOP
DBMS_OUTPUT.put_line (RPAD (o.attr, 20) || RPAD ( o.price, 20));
END LOOP;
END;
Sample execution:
set serveroutput on;
begin
print_inf_value(date'2019-01-01');
end;
Output:
ATTR PRICE
age 2
electronics 0.5
gender 3
hobbies 0.5
homeAddress 7
maritalStatus 1
mobilePhone 5
musicTaste 0.1
socialContacts 1

SQL ORACLE error in trigger

I'm trying to create a trigger and I get the following error:
Error(24,5): PLS-00103: Found the symbol "BEGIN" when it was expected one of the following: * & - + / at loop mod remainder rem and or || multiset. I'm quite a newbie, thanks in advance!
CREATE OR replace TRIGGER ins_livro
instead OF INSERT ON viewLivros
FOR EACH ROW
DECLARE
cnt NUMBER := 10;
biggestID Number;
BEGIN
Select max(exemplar_id) into biggestID from exemplar;
INSERT INTO livro (
id_livro,
nome_livro,
id_editora,
ano,
Preco_Aluguer,
Preco_Compra,
Preco_Multa
)
VALUES (:new.id_livro,
:new.nome_livro,
:new.id_editora,
:new.ano,
:new.Preco_Aluguer,
:new.Preco_Compra,
:new.Preco_Multa
);
WHILE cnt > 0
BEGIN
SET biggestID = biggestID + 1
INSERT INTO exemplar (
id_exemplar,
id_livro
)
VALUES (
:new.biggestID,
:new.id_livro
);
SET cnt = cnt - 1
END;
END;
You are missing the loop and end loop clauses:
WHILE cnt > 0
LOOP
BEGIN
SET biggestID = biggestID + 1
INSERT INTO exemplar (
id_exemplar,
id_livro
)
VALUES (
:new.biggestID,
:new.id_livro
);
SET cnt = cnt - 1
END;
END LOOP;
You have a few mistakes in your syntax. Here it is corrected:
CREATE OR REPLACE TRIGGER ins_livro INSTEAD OF
INSERT ON viewLivros FOR EACH ROW DECLARE cnt NUMBER := 10;
biggestID NUMBER;
BEGIN
SELECT MAX(exemplar_id) INTO biggestID FROM exemplar;
INSERT
INTO livro
(
id_livro,
nome_livro,
id_editora,
ano,
Preco_Aluguer,
Preco_Compra,
Preco_Multa
)
VALUES
(
:new.id_livro,
:new.nome_livro,
:new.id_editora,
:new.ano,
:new.Preco_Aluguer,
:new.Preco_Compra,
:new.Preco_Multa
);
WHILE cnt > 0
LOOP
BEGIN
biggestID := biggestID + 1;
INSERT
INTO exemplar
(
id_exemplar,
id_livro
)
VALUES
(
biggestID,
:new.id_livro
);
cnt := cnt - 1;
END;
END LOOP;
END;
Issues:
You can't use this syntax: SET biggestID = biggestID + 1
You need to use: biggestID := biggestID + 1;
Notice the SET keyword has been removed, = has been changed to := and the line has been terminated with a semicolon.
Also, there's no such thing as :new.biggestID. That is a variable that you've defined in the trigger. It needed to be replaced by just biggestID.
Finally, the BEGIN and END around this block were replaced with LOOP and END LOOP:
SET biggestID = biggestID + 1
INSERT INTO exemplar (
id_exemplar,
id_livro
)
VALUES (
:new.biggestID,
:new.id_livro
);
SET cnt = cnt - 1