xbox360dp
asked on
Use of Cursor in Trigger causing too many row inserts
Gurus,
I have a trigger utilizing a cursor which is inserting too many rows. In the following trigger I'm inserting rows into the GROUP_CALC_TRIGGER2 table from data in the GROUP_CAL_TRIGGER table. I'm getting way too many records after the trigger executes.
Example:
GROUP_CAL_TRIGGER = 432 rows
GROUP_CAL_TRIGGER = 46872 rows
Why is it inserting so many duplicate records??
I have a trigger utilizing a cursor which is inserting too many rows. In the following trigger I'm inserting rows into the GROUP_CALC_TRIGGER2 table from data in the GROUP_CAL_TRIGGER table. I'm getting way too many records after the trigger executes.
Example:
GROUP_CAL_TRIGGER = 432 rows
GROUP_CAL_TRIGGER = 46872 rows
Why is it inserting so many duplicate records??
create or replace
TRIGGER UPDATE_ITEM_ART1_SCO2 before
INSERT OR
UPDATE ON SCO
FOR EACH ROW
DECLARE
scokey number;
element_key number;
VARIANT_KEY NUMBER;
VARIANT_KEY2 NUMBER;
ARTICLE_FIELD_1 VARCHAR2(2000);
CALC_NAME VARCHAR2(2000);
art_fld_hold varchar2(2000);
[b]CURSOR C1 IS
SELECT DISTINCT VARIANT, PROBEZC||'('||ART_FLD1||')' FROM GROUP_CALC_TRIGGER2
WHERE ART_KEY LIKE '%,%'; [/b]
BEGIN
SELECT DISTINCT :NEW.SCOOSZKEYI, PROKEYI , OSZVARKEYI,
SCRBEZC, LISTAGG(ARTANRC,',') WITHIN GROUP (
ORDER BY ARTANRC) INTO SCOKEY, ELEMENT_KEY, VARIANT_KEY,
Calc_name, article_field_1
FROM SCC,
SCR,
OSZ,
SSZ,
ART,
AEZ,
PRO
WHERE PROKEYI = AEZPROKEYI
AND ARTKEYI = AEZARTKEYI
AND :NEW.SCOOSZKEYI = OSZKEYI
AND AEZARTKEYI = :NEW.SCOOBJKEYI
AND OSZSCCKEYI = SCCKEYI
AND SSZSCCKEYI = SCCKEYI
AND SSZSCRKEYI = SCRKEYI
AND OSZVARKEYI = AEZKAVKEYI
AND OSZVARKEYI = ARTKAVKEYI
AND OSZVARKEYI = PROKAVKEYI
and prokavkeyi = artkavkeyi
AND OSZOBJKEYI = PROKEYI
AND OSZTYPS = 170
AND SCRBEZC NOT LIKE '%!%'
GROUP BY :NEW.SCOOSZKEYI,PROKEYI, OSZVARKEYI,
scrbezc;
[b]INSERT INTO GROUP_CAL_TRIGGER (ART_KEY,PROBEZC,ART_FLD1,VARIANT)
values (:new.scoobjkeyi, calc_name,article_field_1,variant_key);
insert into GROUP_CALC_TRIGGER2
SELECT DISTINCT VARIANT, (DBMS_LOB.SUBSTR(WM_CONCAT(ART_KEY))), PROBEZC,
(DBMS_LOB.SUBSTR(WM_CONCAT(ART_FLD1)))
FROM GROUP_CAL_TRIGGER
GROUP BY PROBEZC, VARIANT;[/b]
OPEN C1;
LOOP
FETCH C1 INTO VARIANT_KEY2, ART_FLD_HOLD;
UPDATE ART
SET ARTVF1C = ART_FLD_HOLD
WHERE ARTKAVKEYI = VARIANT_KEY2
and artkeyi = :new.SCOOBJKEYI;
EXIT
WHEN C1%NOTFOUND;
end loop;
close C1;
EXCEPTION
WHEN NO_DATA_FOUND THEN
ARTICLE_FIELD_1 := ARTICLE_FIELD_1;
VARIANT_KEY := VARIANT_KEY;
END;
ASKER
Slighwv,
Why would I need a where clause? If GROUP_CALC_TRIGGER has 300 records then I expect 300 records in GROUP_CALC_TRIGGER2. It seems to be looping over and over causing duplicate records.
Listagg vs wm_concat ... no reason really.
Why would I need a where clause? If GROUP_CALC_TRIGGER has 300 records then I expect 300 records in GROUP_CALC_TRIGGER2. It seems to be looping over and over causing duplicate records.
Listagg vs wm_concat ... no reason really.
You need a where clause :
example when starting with 0 records in both GROUP_CALC_TRIGGER and GROUP_CALC_TRIGGER2
first trigger execution
insert 1 record in GROUP_CALC_TRIGGER > total 1
insert 1 record in GROUP_CALC_TRIGGER2 > total 1
second trigger execution
insert 1 record in GROUP_CALC_TRIGGER > total 2
insert 2 records in GROUP_CALC_TRIGGER2 because there are 2 records in GROUP_CALC_TRIGGER (unless all key values in GROUP_CALC_TRIGGER are the same) > total 3
etc, etc, ....
example when starting with 0 records in both GROUP_CALC_TRIGGER and GROUP_CALC_TRIGGER2
first trigger execution
insert 1 record in GROUP_CALC_TRIGGER > total 1
insert 1 record in GROUP_CALC_TRIGGER2 > total 1
second trigger execution
insert 1 record in GROUP_CALC_TRIGGER > total 2
insert 2 records in GROUP_CALC_TRIGGER2 because there are 2 records in GROUP_CALC_TRIGGER (unless all key values in GROUP_CALC_TRIGGER are the same) > total 3
etc, etc, ....
What flow01 said.
First time through the loop: 1 row each.
Second time: 2 rows in TRIGGER, three rows in TRIGGER2 (The one before and the two new ones from TRIGGER)
Third time: 3 rows in TRIGGER, six rows in TRIGGER2 (The three from before and the 3 rows currently in TRIGGER)
etc...
So you need to selectively insert into TRIGGER2 or clean it out before new rows are added.
Personally, I'm not sure there is a need for the two intermediate tables and the cursor but you know your system better than we do.
First time through the loop: 1 row each.
Second time: 2 rows in TRIGGER, three rows in TRIGGER2 (The one before and the two new ones from TRIGGER)
Third time: 3 rows in TRIGGER, six rows in TRIGGER2 (The three from before and the 3 rows currently in TRIGGER)
etc...
So you need to selectively insert into TRIGGER2 or clean it out before new rows are added.
Personally, I'm not sure there is a need for the two intermediate tables and the cursor but you know your system better than we do.
ASKER
Ok. What would the where clause look like?
ASKER
Or what this the best way to clear out the rows before new rows are inserted?
>>Ok. What would the where clause look like?
I don't know. I don't know your system, data or what you are trying to accomplish.
The only requirements you posted is you are inserting a lot of data into what I believe is a temporary table on insert or update on some other table. Then updating yet another table.
>>Or what this the best way to clear out the rows before new rows are inserted?
I prefer truncate table or from your new question: GLOBAL TEMPORARY TABLES.
It all depends on the problem you are trying to solve. We can only provide information based on what you tell us.
I don't know. I don't know your system, data or what you are trying to accomplish.
The only requirements you posted is you are inserting a lot of data into what I believe is a temporary table on insert or update on some other table. Then updating yet another table.
>>Or what this the best way to clear out the rows before new rows are inserted?
I prefer truncate table or from your new question: GLOBAL TEMPORARY TABLES.
It all depends on the problem you are trying to solve. We can only provide information based on what you tell us.
ASKER
Slightw,
I'm trying to figure out the best option for deleting data from the two temp tables in my trigger (GROUP_CALC_TRIGGER, GROUP_CALC_TRIGGER2).
If Global Temporary Tables is the best option. How would I incorporate them in my current trigger?
I'm trying to figure out the best option for deleting data from the two temp tables in my trigger (GROUP_CALC_TRIGGER, GROUP_CALC_TRIGGER2).
If Global Temporary Tables is the best option. How would I incorporate them in my current trigger?
SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
I completely agree that my approach is inefficient. BUT ... the customer needs the data displayed in a column a particular way.
UPDATE ART
SET ARTVF1C = ART_FLD_HOLD
WHERE ARTKAVKEYI = VARIANT_KEY2
and artkeyi = :new.SCOOBJKEYI;
ARTVF1C = Sum of Products(116704,300255)
Values 116704, 300255 are two different rows of data. Since I'm using a row level trigger I'm unable to concatenate the these values.
I wind up getting: Sum of Products(300255),Sum of Products(116704) for their respective rows. I simply couldn't figure a way to get what I needed without using temp tables.
UPDATE ART
SET ARTVF1C = ART_FLD_HOLD
WHERE ARTKAVKEYI = VARIANT_KEY2
and artkeyi = :new.SCOOBJKEYI;
ARTVF1C = Sum of Products(116704,300255)
Values 116704, 300255 are two different rows of data. Since I'm using a row level trigger I'm unable to concatenate the these values.
I wind up getting: Sum of Products(300255),Sum of Products(116704) for their respective rows. I simply couldn't figure a way to get what I needed without using temp tables.
>>ARTVF1C = Sum of Products(116704,300255)
I really don't see where you SUM anything.
>>Since I'm using a row level trigger
Then why use a row level trigger?
Why not a table level trigger?
The downside is that you'll recompute all sums for all rows in every DML statement.
This might not work for you when you scale it up.
I really don't see where you SUM anything.
>>Since I'm using a row level trigger
Then why use a row level trigger?
Why not a table level trigger?
drop table tab1 purge;
create table tab1(col1 char(1), col2 number);
drop table tab2 purge;
create table tab2(col1 char(1), tab1_col2_sum number);
insert into tab2 values('a',0);
insert into tab2 values('b',0);
commit;
create or replace trigger tab1_trig
after insert on tab1
begin
update tab2 t1a set tab1_col2_sum = (select sum(col2) from tab1 t1b where t1b.col1=t1a.col1);
end;
/
show errors
insert into tab1 values('a',1);
insert into tab1 values('a',1);
insert into tab1 values('a',1);
insert into tab1 values('b',1);
select * from tab2;
The downside is that you'll recompute all sums for all rows in every DML statement.
This might not work for you when you scale it up.
ASKER
slightwv,
I'm not actually looking for the SUM ... Sum of Products is just a name. It could be Price Range ... etc etc
I need to concatenate two different rows of data through the trigger.
Example:
Key|Value
1234 999
4567 888
Result:
XXX(999,888)
I'm not actually looking for the SUM ... Sum of Products is just a name. It could be Price Range ... etc etc
I need to concatenate two different rows of data through the trigger.
Example:
Key|Value
1234 999
4567 888
Result:
XXX(999,888)
Will a table level trigger not work?
>>I need to concatenate two different rows of data through the trigger.
Then why do you need the two temp tables?
It seems like you can generate the values without them but I don't fully understand the requirements.
If you could post a small test case complete with tables, sample data and expected results we can probably post working code.
>>I need to concatenate two different rows of data through the trigger.
Then why do you need the two temp tables?
It seems like you can generate the values without them but I don't fully understand the requirements.
If you could post a small test case complete with tables, sample data and expected results we can probably post working code.
ASKER
slightwv,
Here is a perfect example:
What I want is the following:
ART_CONCAT ELEMENT
------- -------
pants,socks outfit
Here is a perfect example:
SQL> CREATE TABLE "CMKAT"."ART_TEST" ( "ARTKEYI" NUMBER, "ARTBEZC" VARH
AR2(30 CHAR),"ARTVF1C" VARCHAR2(30 CHAR));
Table created.
SQL> CREATE TABLE "CMKAT"."PRO_TEST" ( "PROKEYI" NUMBER, "PROBEZC" VARCHAR2(30
CHAR), "PROVF1C" VARCHAR2(30 CHAR)) ;
Table created.
SQL> CREATE TABLE "CMKAT"."LOOKUP" ( "LOOKUP_ARTKEYI" NUMBER, "LOOKUP_PROKEYI
" NUMBER);
Table created.
SQL> Insert into ART_TEST (ARTKEYI,ARTBEZC,ARTVF1C) values (3001,'pants',null);
1 row created.
SQL> Insert into ART_TEST (ARTKEYI,ARTBEZC,ARTVF1C) values (3002,'socks',null);
1 row created.
SQL> Insert into PRO_TEST (PROKEYI,PROBEZC,PROVF1C) values (4001,'outfit',null);
1 row created.
SQL> Insert into LOOKUP(LOOKUP_ARTKEYI,LOOKUP_PROKEYI) values (3001,4001);
1 row created.
SQL> Insert into LOOKUP(LOOKUP_ARTKEYI,LOOKUP_PROKEYI) values (3002,4001);
1 row created.
SQL> commit;
Commit complete.
SQL> SELECT LISTAGG(ARTBEZC,',') WITHIN GROUP (
ORDER BY ARTKEYI) CON,
PROBEZC GRP
FROM ART_TEST,
PRO_TEST,
LOOKUP
WHERE ART_TEST.ARTKEYI = LOOKUP.LOOKUP_ARTKEYI
AND PRO_TEST.PROKEYI = LOOKUP.LOOKUP_PROKEYI
GROUP BY PROBEZC;
CON GRP
---------------- --------
pants,socks outfit
CREATE OR REPLACE TRIGGER Trigg_Test before
INSERT OR
UPDATE ON ART_TEST FOR EACH ROW
DECLARE
ARTICLE_NAME VARCHAR2(100);
ELEMENT_NAME VARCHAR2(100);
BEGIN
SELECT LISTAGG(:NEW.ARTBEZC,',') WITHIN GROUP (
ORDER BY :new.ARTKEYI) CON,
PROBEZC GRP
INTO article_name,
element_name
FROM
PRO_TEST,
LOOKUP
WHERE :new.ARTKEYI = LOOKUP.LOOKUP_ARTKEYI
AND PRO_TEST.PROKEYI = LOOKUP.LOOKUP_PROKEYI
GROUP BY PROBEZC;
INSERT INTO TEST_TABLE_FOR_TRIGGER VALUES
(ARTICLE_NAME,ELEMENT_NAME);
END;
SQL> UPDATE ART_TEST SET ARTVF1C = 'TEST'
where artkeyi in (3001,3002);
2 rows updated.
SQL> commit;
Commit complete.
SQL> SELECT * FROM TEST_TABLE_FOR_TRIGGER;
ART_CONCAT ELEMENT
------- -------
pants outfit
socks outfit
What I want is the following:
ART_CONCAT ELEMENT
------- -------
pants,socks outfit
Based on the sample provided, it looks to me like you are overcomplicating this. All you really need is a list of the rows updated in a temporary table. Since I am assuming that more than one session could be updating the data, ideally you need a global temporary table. Using your sample, I would create it this way:
CREATE global TEMPORARY TABLE test_table_for_trigger
(
artkeyi NUMBER
)
ON COMMIT DELETE ROWS;
Then the trigger becomes simply:CREATE OR replace TRIGGER trigg_test
BEFORE INSERT OR UPDATE ON art_test
FOR EACH ROW
BEGIN
INSERT INTO test_table_for_trigger
VALUES (:new.artkeyi);
END;
/
Then once you do the update that you provided in the sample, query the results with this:SELECT Listagg(art_test.artbezc, ',')
within GROUP ( ORDER BY art_test.artkeyi) CON,
pro_test.probezc GRP
FROM art_test,
pro_test,
lookup,
test_table_for_trigger
WHERE art_test.artkeyi = lookup.lookup_artkeyi
AND pro_test.prokeyi = lookup.lookup_prokeyi
AND test_table_for_trigger.artkeyi = lookup.lookup_artkeyi
GROUP BY pro_test.probezc;
Using your sample and that code, I got the results you posted.
ASKER
johnsone,
I don't see how this will work given the example that I gave you. I values must already be concatenated in the column.
So that when I query the table I get the correct results.
I don't see how this will work given the example that I gave you. I values must already be concatenated in the column.
So that when I query the table I get the correct results.
What happens if you issue two updates back to back without a commit or rollback in between?
...
UPDATE ART_TEST SET ARTVF1C = 'TEST' where artkeyi in (3001,3002);
UPDATE ART_TEST SET ARTVF1C = 'TEST' where artkeyi in (3001,3002);
commit;
...
If you expect:
pants,pants,socks,socks outfit
I think I have a solution.
If you expect something different, then there is a flaw in the requirement.
...
UPDATE ART_TEST SET ARTVF1C = 'TEST' where artkeyi in (3001,3002);
UPDATE ART_TEST SET ARTVF1C = 'TEST' where artkeyi in (3001,3002);
commit;
...
If you expect:
pants,pants,socks,socks outfit
I think I have a solution.
If you expect something different, then there is a flaw in the requirement.
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
slightwv,
This works great! Could help me incorporate this logic into the trigger in my original question?
This works great! Could help me incorporate this logic into the trigger in my original question?
>>Could help me incorporate this logic into the trigger in my original question?
Not really. Since I don't have the tables or values to test with, anything I would post would be a guess at best.
What I can do is say that you don't need the cursor or two temp tables.
Just create the global temporary table in place of your two temp tables.
Then replace the insert into test_table_for_trigger in my code with the UPDATE in your original trigger.
Not really. Since I don't have the tables or values to test with, anything I would post would be a guess at best.
What I can do is say that you don't need the cursor or two temp tables.
Just create the global temporary table in place of your two temp tables.
Then replace the insert into test_table_for_trigger in my code with the UPDATE in your original trigger.
You also probably don't need the:
delete from test_table_for_trigger where element=v_element_name;
Since you are updating a single column, just keep updating... The final update should have the correct string.
I did the delete because your test case did an insert so I had one row for every trigger fire.
delete from test_table_for_trigger where element=v_element_name;
Since you are updating a single column, just keep updating... The final update should have the correct string.
I did the delete because your test case did an insert so I had one row for every trigger fire.
For what I posted, why does the table need to have the concatenated values in it? You either query it to concatenate the values at that point, or create a view that does it for you.
My guess to the problem is here:
insert into GROUP_CALC_TRIGGER2
SELECT DISTINCT ...
For every row affected by the DML on SCO, you insert into GROUP_CALC_TRIGGER then do the above insert into GROUP_CALC_TRIGGER2 selecting EVERYTHING from GROUP_CALC_TRIGGER.
There is no where clause...