Skip to Main Content

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question, MEhmet.

Asked: January 26, 2003 - 1:54 pm UTC

Last updated: January 30, 2013 - 1:43 pm UTC

Version: 9.0.1

Viewed 10K+ times! This question is

You Asked

ptyp_MyTypeList is MY Local variable..
MY_COLLECTION_TYPE is an collection obj which has elements
col1,col2,col3,col4.

In PL/SQL Code I try to update table_1 table using values in my collection.

UPDATE
(SELECT h.aa h_aa, z.bb z_bb
FROM table_1 h,
(SELECT col1, col2, col3, col4 bb
FROM the (SELECT CAST( ptyp_MyTypeList AS MY_COLLECTION_TYPE )
FROM dual)) z
WHERE h.col1 = z.col1
AND h.col2 = z.col2
AND h.col3 = z.col3
)
SET h_aa = z_bb

I think it is not valid.
Should I ask why?

Is there any way to update a table using collection without FOR LOOP or FORALL LOOP?

Thanks
meksi

and Tom said...

there is a problem with key preservation in this example -- there isn't any. A collection type cannot have a primary key -- hence Oracle doesn't know that your collection doesn't have it in:


ptyp_MyTypelist := my_collection_type( my_scalar_type( 1,1,1,55 ),
my_scalar_type( 1,1,1,66 ) );


You see the results of that update would be ambigous -- would H_AA have 55 or 66 after the update?

So, the collection would need a primary key, but collections cannot have primary keys -- hence -- no go. You could:

ops$tkyte@ORA920> create type my_scalar_type as object
2 ( col1 number, col2 number, col3 number, col4 number )
3 /
Type created.

ops$tkyte@ORA920> create type my_collection_type as table of my_scalar_type
2 /
Type created.

ops$tkyte@ORA920> drop table table_1;
Table dropped.

ops$tkyte@ORA920> create table table_1
2 ( col1 number, col2 number, col3 number, col4 number,
3 primary key(col1,col2,col3) )
4 /
Table created.

ops$tkyte@ORA920> insert into table_1
2 select rownum, rownum, rownum, null from all_users
3 where rownum < 5;

4 rows created.

ops$tkyte@ORA920> declare
2 ptyp_MyTypelist my_collection_type :=
3 my_collection_type( my_scalar_type( 1,1,1,55 ),
4 my_scalar_type( 2,2,2,66 ) );
5 begin
6 merge into TABLE_1 H
7 using (select col1, col2, col3, col4 bb
8 from TABLE( cast( ptyp_myTypelist as my_collection_type ) ) ) z
9 on (h.col1 = z.col1 and h.col2 = z.col2 and h.col3 = z.col3 )
10 when matched then
11 update set h.col4 = z.bb
12 when not matched then
13 insert (col1) values (null);
14 end;
15 /


PL/SQL procedure successfully completed.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select * from table_1;

COL1 COL2 COL3 COL4
---------- ---------- ---------- ----------
1 1 1 55
2 2 2 66
3 3 3
4 4 4

now that did the update for you -- why I used INSERT NULL -- we'll see in a moment -- first, we'll see what happens when we try to update with DUPS


ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
2 ptyp_MyTypelist my_collection_type :=
3 my_collection_type( my_scalar_type( 1,1,1,55 ),
4 my_scalar_type( 1,1,1,66 ) );

5 begin
6 merge into TABLE_1 H
7 using (select col1, col2, col3, col4 bb
8 from TABLE( cast( ptyp_myTypelist as my_collection_type ) ) ) z
9 on (h.col1 = z.col1 and h.col2 = z.col2 and h.col3 = z.col3 )
10 when matched then
11 update set h.col4 = z.bb
12 when not matched then
13 insert (col1) values (null);
14 end;
15 /
declare
*
ERROR at line 1:
ORA-30926: unable to get a stable set of rows in the source tables
ORA-06512: at line 6


we caught the ambiguity and FAIL the update -- no telling what value should be used, so we kill it. Now, why did I do the insert NULL -- to make this fail:




ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
2 ptyp_MyTypelist my_collection_type :=
3 my_collection_type( my_scalar_type( 1,1,1,55 ),
4 my_scalar_type( 100,1,1,66 ) );

5 begin
6 merge into TABLE_1 H
7 using (select col1, col2, col3, col4 bb
8 from TABLE( cast( ptyp_myTypelist as my_collection_type ) ) ) z
9 on (h.col1 = z.col1 and h.col2 = z.col2 and h.col3 = z.col3 )
10 when matched then
11 update set h.col4 = z.bb
12 when not matched then
13 insert (col1) values (null);
14 end;
15 /
declare
*
ERROR at line 1:
ORA-01400: cannot insert NULL into ("OPS$TKYTE"."TABLE_1"."COL1")
ORA-06512: at line 6

I did not want to CREATE rows (maybe you do -- then change the insert), just update the existing ones. Hence I either need to make sure I only have valid entries in the collection (insert NULL does that nicely) or I can use a subquery to filter the collection:

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
2 ptyp_MyTypelist my_collection_type :=
3 my_collection_type( my_scalar_type( 3,3,3,77 ),
4 my_scalar_type( 100,1,1,66 ) );
5 begin
6 merge into TABLE_1 H
7 using (select col1, col2, col3, col4 bb
8 from TABLE( cast( ptyp_myTypelist as my_collection_type ) )
9 where (col1, col2, col3) IN (select col1,col2,col3 from table_1) ) z
10 on (h.col1 = z.col1 and h.col2 = z.col2 and h.col3 = z.col3 )
11 when matched then
12 update set h.col4 = z.bb
13 when not matched then
14 insert (col1) values (null);
15 end;
16 /

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> select * from table_1;

COL1 COL2 COL3 COL4
---------- ---------- ---------- ----------
1 1 1 55
2 2 2 66
3 3 3 77
4 4 4



Rating

  (16 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

ORA-30926

A reader, June 19, 2003 - 9:16 am UTC

tom,

Please explain me why i am getting this error. frankly i did not understand much with the above explanation - sorry about that.

what should i do to avoid this error

17:44:38 ge$matrix@links.us.oracle.com>create table tb_today2 as select * from tb_today;
17:46:32 ge$matrix@links.us.oracle.com>create table tb_today3 as select * from tb_today;

18:27:56 ge$matrix@links.us.oracle.com>sta c:\ge\links\proc\practice\merge1.sql;
begin
*
ERROR at line 1:
ORA-30926: unable to get a stable set of rows in the source tables
ORA-06512: at line 2


18:27:58 ge$matrix@links.us.oracle.com>select count(*) from tb_today2 e1, tb_today3 e2 where
18:28:44 2 e1.customer_index_num=e2.customer_index_num and e1.requisition_num=e2.requisition_num and e1.system_origin_nam=e2.system_origin_nam and e1.line_num=e2.line_num and e1.sequence_num=e2.sequence_num;

COUNT(*)
==========
9873

18:28:55 ge$matrix@links.us.oracle.com>alter table tb_today3 add flag varchar2(2);


--merge1.sql--
begin
merge into tb_today2 e1 using tb_today3 e2 on (e1.customer_index_num=e2.customer_index_num and e1.requisition_num=e2.requisition_num and e1.system_origin_nam=e2.system_origin_nam and e1.line_num=e2.line_num and e1.sequence_num=e2.sequence_num)
when matched then
update set e1.flag='U'
when not matched then
insert (customer_index_num) values (e2.customer_index_num);
commit;
end;


Thanks for your consideration.

Tom Kyte
June 19, 2003 - 1:02 pm UTC

consider:

ops$tkyte@ORA920LAP> create table t1 ( x int, y int );
Table created.

ops$tkyte@ORA920LAP> create table t2 ( x int, y int );
Table created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> insert into t1 values ( 1, 1 );
1 row created.

ops$tkyte@ORA920LAP> insert into t2 values ( 1, 2 );
1 row created.

ops$tkyte@ORA920LAP>
ops$tkyte@ORA920LAP> merge into t1 using t2
  2  on ( t1.x = t2.x )
  3  when matched then update set t1.y = t2.y
  4  when not matched then insert (x,y) values ( t2.x, t2.y);
1 row merged.

<b>Ok, here it is clear what will happen right -- we'll do an update of Y from 1 to 2.  BUT</b>

ops$tkyte@ORA920LAP> insert into t2 values (1, 3 );
1 row created.

<b>Now tell me -- what SHOULD happen now?  should y be 2 or 3 at the end?  </b>

ops$tkyte@ORA920LAP> merge into t1 using t2
  2  on ( t1.x = t2.x )
  3  when matched then update set t1.y = t2.y
  4  when not matched then insert (x,y) values ( t2.x, t2.y);
merge into t1 using t2
           *
ERROR at line 1:
ORA-30926: unable to get a stable set of rows in the source tables


<b>that is what this means.  It means that your merge key of 

(e1.customer_index_num=e2.customer_index_num and 
e1.requisition_num=e2.requisition_num and 
e1.system_origin_nam=e2.system_origin_nam and e1.line_num=e2.line_num and 
e1.sequence_num=e2.sequence_num)

has DUPLICATES in tb_today3 e2 and we don't know WHICH of the N rows in e2 should be used to update the row in e1.

</b>


Your "on" clause should be "on to a unique key" 

A reader, June 19, 2003 - 9:20 am UTC

tom,
please ignore this line in the above message..
18:28:55 ge$matrix@links.us.oracle.com>alter table tb_today3 add flag
varchar2(2);

both the tables are of same strucutre.

thanks again

Excellent - yet an another thankyou!

A reader, June 19, 2003 - 1:51 pm UTC


Merge : output

arjun, July 03, 2003 - 2:12 pm UTC

hi: tom,

excellent as always:

I tried examples using merge : I get

"Operation 189 succeeded." instead of "n rows merged"

following is my version:

BANNER
------------------------------------------------------------
Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production
PL/SQL Release 9.2.0.3.0 - Production
CORE 9.2.0.3.0 Production
TNS for Solaris: Version 9.2.0.3.0 - Production
NLSRTL Version 9.2.0.3.0 - Production

please let me know.


regards

arjun

Tom Kyte
July 03, 2003 - 8:10 pm UTC

but what is your sqlplus version -- oh, that is 8i or before....

ok, sqlplus at that release level has no clue what "merge" is, hence is just says "whatever you typed was amazing and it worked"

excellent explanation

A reader, October 01, 2003 - 6:53 am UTC

hi tom,

hats off to your amazing explanations and solution. The above error as u explained is caused mainly due to having multiple rows in the source table and if we use MERGE statement.

We are facing a strange situation. We have an oracle stored PROCEDURE that has simple update
statements AS follows......

UPDATE BAK_LYENTITYPROPERTY
SET keyname = 'CLASSNAME'
WHERE UPPER(name) IN ( 'GENERALCLASSKEY','LIMITEDCLASSKEY','LLCCLASSKEY',
'DEFAULTNONRECOURSEPARTNERCLASSKEY','DEFAULTOTHERPARTNERCLASSKEY',
'DEFAULTQUALIFIEDPARTNERCLASSKEY','DEFAULTSCHKPARTNERCLASSKEY');
-- END 003

--update LYENTITY related keyvalues
UPDATE BAK_LYENTITYPROPERTY T
SET keyvalue = ( SELECT F.entitykey
FROM LYENTITY F
WHERE NVL(RTRIM(F.dcorp), ' ') = NVL(RTRIM(T.value), ' ') --002
AND F.enterprisekey = v_enterprisekey
AND T.keyname = 'DCORP' ) --002
WHERE EXISTS ( SELECT 1
FROM LYENTITY F
WHERE NVL(RTRIM(F.dcorp), ' ') = NVL(RTRIM(T.value), ' ') --002
AND F.enterprisekey = v_enterprisekey
AND T.keyname = 'DCORP' ); --002

..............

some how, we get the following error message.

.... ORA-30926: unable to get a stable set of rows in the source tables

reading ur explanations i could conclude that
the above error msg IS meant FOR merge statements WHERE the join condition
returns multiple ROWS FROM the source table. But our PROCEDURE uses no MERGE statment
AND we are working ON oracle version 8.1.7 IN which merge STATEMENT IS NOT available AT all.

Can u let me know the cause of this error in a situation other than the MERGE statement. Or can we replicate the behaviour of a MERGE statment , using simple updates/inserts/selects ???

thanx ,nikhil

Tom Kyte
October 01, 2003 - 8:44 am UTC

be there a bitmap index involved?

Merge strange behaviour

karma, December 11, 2003 - 8:10 pm UTC

here i have simple merger but its not doing as expected:
MERGE INTO a_acct xx
USING
(
(SELECT a.firm_c,a.brch_c,a.base_c,sysdate,a.citz_c
FROM a_acct_mstr a
WHERE (a.firm_c,a.brch_c,a.base_c) NOT IN (SELECT firm_c,brch_c,base_c FROM i_acct_mstr)
)
UNION
(SELECT a.firm_c,a.brch_c,a.base_c,sysdate cycle_d,a.citz_c
FROM i_acct_mstr i, a_acct_mstr a
WHERE a.firm_c = i.firm_c
AND a.brch_c = i.brch_c
AND a.base_c = i.base_c
AND (a.citz_c <> i.citz_c or i.citz_c IS NULL)
AND a.citz_c IN ('3', '4','B','C','D')

)
) yy
ON (xx.firm_c = yy.firm_c and xx.brch_c = yy.brch_c and xx.base_c = yy.base_c)
WHEN matched THEN
UPDATE SET xx.eff_date = sysdate+10
WHEN not matched THEN
INSERT (firm_c,brch_c,base_c,eff_date,doc_category,doc_code,doc_status,doc_short_name,
doc_code_desc,source_sys,prtn_id,insert_tmst,update_tmst)
VALUES (yy.firm_c,yy.brch_c,yy.base_c,sysdate,'XXX','-',yy.citz_c,'-',
'-','BVVV','RRRR',sysdate,sysdate) ;

Here is the scoop:
select before union return 1 row I.e i got new row
select after union returns no rows i.e. nothing changed
A_acct is EMPTY.
First time i ran merge it says succesfully completed not rows affected. Now since i got 1 NEW row and there is nothing in a_acct table should'nt it insert ? It does not.

So, I changed data so that 2nd part of SQL ( after Union) gets me 1 row.
Now i ran merge stmt. and it says 2 rows affected. It inserted 2 rows and this is correct.

Now i changed back data so that 2nd part of union returns no data and first part of union returns 1 row and ran merge again and it should update 1 row since this row do exist in the a_acct table now because of 2nd run but merge says no rows affected.

I am going crazy and wondering if i am doing something wrong?

Tom Kyte
December 13, 2003 - 10:23 am UTC

hows about a 100% complete, yet as concise as possible example. including little create tables, the inserts needed, etc -- you know, just like I do in my examples!

remember -- CONCISE is as important as COMPLETE. get rid of everything that is not relevant to the example, use as few columns as possible. I find many (most) times that in the course of making a tiny example -- I find the errors of my ways (I find the mistake i was making whilst in the course of trying to reproduce it with as few moving pieces as possible)

malcolm, February 23, 2004 - 7:26 am UTC

Tom, this thread has been very useful me. Thanks.

I like the way that Oracle can automagically detect whether the result of a merge will be ambiguous, and throws a ORA-30926 if it's not.

But why can't it do a similar thing with updates of views? What I mean is that it would be nice to tell Oracle that a view is key-preserved, even if it's not provable to the server.

Something like this...

update /*+is_key_preserved_hint*/
(select t1.y t1_y, t2.y t2_y
from t1, t2
where t1.x = t2.x)
set t1_y = t2_y

... and Oracle tries the update, but it fails if the view really isn't key preserved.

Then we wouldn't need to use the merge workaround.

Tom Kyte
February 23, 2004 - 7:53 am UTC

there is a hint, hint is undocumented, therefore I don't talk about bypass_ujvc

bypass updatable join view check?

malcolm, February 23, 2004 - 10:44 am UTC

I notice this hint is used for MV refreshes, so it must be reasonably safe.

Of course I won't be using it for any production code, but it will be useful for ad-hoc updates.

Since the functionality is there, it would be nice if Oracle fully supported it.

Tom Kyte
February 23, 2004 - 10:49 am UTC

MERGE does it.

Especially in 10g when we can skip the INSERT portion of the MERGE all together.

ORA-01733: virtual column not allowed here

A reader, March 18, 2009 - 12:10 pm UTC

Dear Tom,

I did the following update on a collection (table of object)

UPDATE ( SELECT *
FROM TABLE ( my_collection) ) dd
SET dd.aa = 5
, dd.bb = 6
, dd.cc = 7
, dd.ee = 8
, dd.ff = 9
WHERE dd.id = 100
AND dd.yy = 200;
And I get ORA-01733: virtual column not allowed here.

Isn't possible to update a collection like above?

Thanks a lot for your help
Tom Kyte
March 19, 2009 - 10:10 am UTC

no, it is not.

you could

ops$tkyte%ORA10GR2> create type myScalarType as object
  2  ( a number, b number, c number, x number, y number )
  3  /

Type created.

ops$tkyte%ORA10GR2> create type myTableType as table of myScalarType
  2  /

Type created.

ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> declare
  2          l_collection1 myTableType := myTableType( myScalarType( 100, 200, 300, 100, 200 ),
  3                                                    myScalarType( 100, 200, 300, 10, 20 ) ) ;
  4          l_collection2 myTableType;
  5  begin
  6          select myScalarType( case when x = 100 and y = 200 then 5 else a end,
  7                               case when x = 100 and y = 200 then 6 else b end,
  8                               case when x = 100 and y = 200 then 7 else c end,
  9                                                   x, y )
 10           bulk collect into l_collection2
 11           from table( l_collection1 );
 12
 13           for i in 1 .. l_collection2.count
 14           loop
 15                  dbms_output.put_line( l_collection2(i).a );
 16                  dbms_output.put_line( l_collection2(i).b );
 17                  dbms_output.put_line( l_collection2(i).c );
 18                  dbms_output.put_line( l_collection2(i).x );
 19                  dbms_output.put_line( l_collection2(i).y );
 20                  dbms_output.put_line( '------------------' );
 21          end loop;
 22  end;
 23  /
5
6
7
100
200
------------------
100
200
300
10
20
------------------

PL/SQL procedure successfully completed.


but it is likely easier just to iterate over the collection elements.

Manipulate Collection in Loop before inserting into table

Anand Singh, October 22, 2011 - 6:30 am UTC


1. Created four data structure
==============================

create type EMAIL_LIST as object
(
NAME Varchar2(300),
EMAIL Varchar2(300)
)
/

CREATE TYPE EMAIL_ADDRESS IS TABLE OF EMAIL_LIST
/

create type EMAIL_ARRAYTYPE as object
(
COL1 Varchar2(300),
COL2 Varchar2(300)
)
/

CREATE TYPE EMAIL_ARRAY IS TABLE OF EMAIL_ARRAYTYPE
/


2. Created a tables using above data structure
==============================

CREATE TABLE EMAIL_DATA_DETAIL
(
EMAILDATAID NUMBER(8),
emailformatid VARCHAR2(8),
POPTIME DATE,
PUSHTIME DATE,
PUSHED VARCHAR2(1),
ERRMSG VARCHAR2(4000),
FROM_LIST EMAIL_ADDRESS,
TO_LIST EMAIL_ADDRESS,
NUM_COL1 NUMBER(18,2),
NUM_COL2 NUMBER(18,2),
INT_COL1 NUMBER(8),
PERCENT_COL1 NUMBER(10,7),
VCHAR_COL1 VARCHAR2(300),
VCHAR_COL2 VARCHAR2(300),
VCHAR_COL3 VARCHAR2(300),
ARRAY_COL1 EMAIL_ARRAY,
ARRAY_COL2 EMAIL_ARRAY
)
NESTED TABLE FROM_LIST STORE AS EMAIL_DATA_DETAIL_FROM_LIST,
NESTED TABLE TO_LIST STORE AS EMAIL_DATA_DETAIL_TO_LIST,
NESTED TABLE ARRAY_COL1 STORE AS EMAIL_DATA_DETAIL_ARRAY_COL1,
NESTED TABLE ARRAY_COL2 STORE AS EMAIL_DATA_DETAIL_ARRAY_COL2
/


3. Want to populate a tables with all details
==============================================
begin
for i in (select username, email
from user_master
where <<some condition>>
loop
1. want to populate some collection with username and email
end loop;
2. Want to create a single record in email_data_detail table wherein TO_LIST having all username and email reterived in step1

commit;
end;
/

Is there any way in PL/SQL wherein i can populate some data structure (PL/SQL tables etc.) in the loop and can
insert single record into a tables for further use.


Thanks In advance for your help and managing time from your busy schedule.




Tom Kyte
October 22, 2011 - 8:18 pm UTC

yes there is, have you read the plsql language guide? It pretty much covers collections in great detail.

ops$tkyte%ORA11GR2> CREATE TABLE EMAIL_DATA_DETAIL
  2  (
  3  EMAILDATAID        NUMBER(8),
  4  FROM_LIST          EMAIL_ADDRESS,
  5  TO_LIST            EMAIL_ADDRESS
  6  )
  7  NESTED TABLE FROM_LIST STORE AS EMAIL_DATA_DETAIL_FROM_LIST,
  8  NESTED TABLE TO_LIST  STORE AS EMAIL_DATA_DETAIL_TO_LIST
  9  /

Table created.

ops$tkyte%ORA11GR2> 
ops$tkyte%ORA11GR2> 
ops$tkyte%ORA11GR2> declare
  2          l_rec email_data_detail%rowtype;
  3  begin
  4          for x in (select rownum r from all_users where rownum <= 2)
  5          loop
  6                  l_rec.emaildataid := x.r;
  7                  l_rec.from_list := email_address();
  8                  l_rec.from_list.extend(3);
  9                  for i in 1 .. 3
 10                  loop
 11                          l_rec.from_list(i) := email_list( 'name_' || i, 'email_'||i );
 12                  end loop;
 13  
 14                  select email_list( username, to_char(created) ) bulk collect into l_rec.to_list from all_users where rownum < 5;
 15  
 16                  insert into email_data_detail values L_REC;
 17          end loop;
 18  end;
 19  /

PL/SQL procedure successfully completed.

ops$tkyte%ORA11GR2> 
ops$tkyte%ORA11GR2> 
ops$tkyte%ORA11GR2> select * from email_data_detail;

EMAILDATAID
-----------
FROM_LIST(NAME, EMAIL)
-------------------------------------------------------------------------------
TO_LIST(NAME, EMAIL)
-------------------------------------------------------------------------------
          1
EMAIL_ADDRESS(EMAIL_LIST('name_1', 'email_1'), EMAIL_LIST('name_2', 'email_2'),
 EMAIL_LIST('name_3', 'email_3'))
EMAIL_ADDRESS(EMAIL_LIST('SYS', '05-SEP-10'), EMAIL_LIST('SYSTEM', '05-SEP-10')
, EMAIL_LIST('OUTLN', '05-SEP-10'), EMAIL_LIST('DIP', '05-SEP-10'))

          2
EMAIL_ADDRESS(EMAIL_LIST('name_1', 'email_1'), EMAIL_LIST('name_2', 'email_2'),
 EMAIL_LIST('name_3', 'email_3'))
EMAIL_ADDRESS(EMAIL_LIST('SYS', '05-SEP-10'), EMAIL_LIST('SYSTEM', '05-SEP-10')
, EMAIL_LIST('OUTLN', '05-SEP-10'), EMAIL_LIST('DIP', '05-SEP-10'))





HOWEVER, I would strongly encourage you to NOT DO THIS. Just use regular tables, rows and columns, joins.. I strongly encourage you to NOT DO THIS.

Merge & Update

Rajeshwaran Jeyabal, October 22, 2011 - 9:13 pm UTC

Tom:

I was exercising your example on First post and ended up with some doubts.

rajesh@ORA10GR2> declare
  2   l_type myobjntt := myobjntt(myobj(1,1,1,1), myobj(2,2,2,2));
  3  begin
  4   update
  5   (
  6    select t.col1 as t_col1,
  7        t.col2 as t_col2,
  8        t.col3 as t_col3,
  9        t.col4 as t_col4,
 10        my.col1 as my_col1,
 11        my.col2 as my_col2,
 12        my.col3 as my_col3,
 13        my.col4 as my_col4
 14    from t, table(l_type) my
 15    where t.col1 = my.col1
 16   )
 17   set t_col4 = my_col4;
 18  end;
 19  /
declare
*
ERROR at line 1:
ORA-01779: cannot modify a column which maps to a non key-preserved table
ORA-06512: at line 4


Elapsed: 00:00:00.14
rajesh@ORA10GR2> 
rajesh@ORA10GR2> declare
  2   l_type myobjntt := myobjntt(myobj(1,1,1,1), myobj(2,2,2,2));
  3  begin
  4   merge into t using 
  5   (select * from table(l_type)) t1
  6   on (t.col1 = t1.col1)
  7   when matched then
  8    update set t.col4 = t1.col4;
  9  end;
 10  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.09
rajesh@ORA10GR2> 
rajesh@ORA10GR2> select * from t;

      COL1       COL2       COL3       COL4
---------- ---------- ---------- ----------
         1          1          1          1
         2          2          2          2
         3          3          3

Elapsed: 00:00:00.12
rajesh@ORA10GR2> 
rajesh@ORA10GR2>


Questions:

1) Is that MERGE and UPDATE performs differently? Can you help me to understand why UPDATE returns ORA-01779?

(Here is the script I used for this example)

drop table t purge;

create table t(
col1 number,
col2 number,
col3 number,
col4 number);

insert into t values(1,1,1,null);
insert into t values(2,2,2,null);
insert into t values(3,3,3,null);

create or replace type 
myobj as object(col1 number,
col2 number,
col3 number,
col4 number);
/

create or replace type myobjntt is table of myobj;
/

alter table t add constraint t_pk primary key(col1);

Tom Kyte
October 24, 2011 - 4:42 am UTC

What this is saying is that

"the rows in T, as produced by the selection in the update, could come out more than once."

"a single row in T might appear many times, not just once, in the output"


Since that can happen (there is nothing preventing it, I know your example does not have it happen, but the fact is - IT CAN happen), we do not permit the update - since the value to which t_col4 would be set to would ultimately depend on the order the rows were processed in (it would not be deterministic).

The merge can also suffer from this issue - but it does the check at runtime by design. Not compile time.

(Hint to you Rajesh, if you are going to change my names, supply the ENTIRE TEST CASE, the entire thing, just like I always do. Don't make us guess what your structures might look like. Put is all together in one concise location - remove the ambiguity)


ops$tkyte%ORA11GR2> create type my_scalar_type as object
  2  ( col1 number, col2 number, col3 number, col4 number )
  3  /

Type created.

ops$tkyte%ORA11GR2> 
ops$tkyte%ORA11GR2> create type my_collection_type as table of my_scalar_type
  2  /

Type created.

ops$tkyte%ORA11GR2> 
ops$tkyte%ORA11GR2> create table t
  2  ( col1 number, col2 number, col3 number, col4 number,
  3    primary key(col1,col2,col3) )
  4  /

Table created.

ops$tkyte%ORA11GR2> 
ops$tkyte%ORA11GR2> insert into t values(1,2,3,4);

1 row created.

ops$tkyte%ORA11GR2> 
ops$tkyte%ORA11GR2> declare
  2   l_type my_collection_type := my_collection_type(my_scalar_type(1,1,1,1), my_scalar_type(2,2,2,2));
  3  begin
  4   merge into t using
  5   (select * from table(l_type)) t1
  6   on (t.col1 = t1.col1)
  7   when matched then
  8    update set t.col4 = t1.col4;
  9  end;
 10  /

PL/SQL procedure successfully completed.

ops$tkyte%ORA11GR2> select * from t;

      COL1       COL2       COL3       COL4
---------- ---------- ---------- ----------
         1          2          3          1

ops$tkyte%ORA11GR2> 
ops$tkyte%ORA11GR2> declare
  2   l_type my_collection_type := my_collection_type(my_scalar_type(1,1,1,3), my_scalar_type(1,2,2,1));
  3  begin
  4   merge into t using
  5   (select * from table(l_type)) t1
  6   on (t.col1 = t1.col1)
  7   when matched then
  8    update set t.col4 = t1.col4;
  9  end;
 10  /
declare
*
ERROR at line 1:
ORA-30926: unable to get a stable set of rows in the source tables
ORA-06512: at line 4


the merge failed the second time around because the database realized the modification was "flaky" - not deterministic.

Merge & Update

Rajeshwaran Jeyabal, October 24, 2011 - 1:08 pm UTC

Yep, This is what i needed Tom, Thanks.
The merge can also suffer from this issue - but it does the check at runtime by design. Not compile time

Error while updating collection

Girish, January 25, 2013 - 4:28 am UTC

Hi Tom,

I am doing following steps to update why the error is occuring 

SQL> create or replace type myTableType as table of number;
    /
Type created.

SQL> create or replace function str2tbl( p_str in varchar2 ) return myTableType
   as
       l_str   long default p_str || ',';
       l_n        number;
       l_data    myTableType := myTabletype();
   begin
       loop
           l_n := instr( l_str, ',' );
           exit when (nvl(l_n,0) = 0);
           l_data.extend;
           l_data( l_data.count ) := ltrim(rtrim(substr(l_str,1,l_n-1)));
           l_str := substr( l_str, l_n+1 );
       end loop;
       return l_data;
   end;
   /

  1  merge into test_2013 a
  2  using table(cast( str2tbl('ABC,DEF,XYZ,PQR') as mytableType )) b
  3  on (1=1)
  4  when matched then
  5* update set c2=b.column_value
TEST@orcl@25-JAN-13> /
merge into test_2013 a
*
ERROR at line 1:
ORA-30926: unable to get a stable set of rows in the source tables  

Regards,
Girish

Tom Kyte
January 30, 2013 - 1:43 pm UTC

it wouldn't matter if you used a real table, the collection is a red herring here (not relevant)


you are merging on "1=1" so every row in test_2013 is being joined with every row in B

you are trying to update the first row in test_2013 to one of ABC, DEF, XYZ, PQR - and then trying to update the FIRST ROW (again) to one of ABC, DEF, XYZ, PQR (minus whatever you just set it to) and so on.


so the result of the merge is not deterministic. depending on the arbitrary ordering of the rows in the join - you'd get different answers each time you ran it.

hence the error.

ops$tkyte%ORA11GR2> create table t ( c2 varchar2(10) );

Table created.

ops$tkyte%ORA11GR2> insert into t values ( null );

1 row created.

ops$tkyte%ORA11GR2> create table b
  2  as
  3  select 'ABC' column_value from dual union all
  4  select 'DEF' column_value from dual union all
  5  select 'XYZ' column_value from dual union all
  6  select 'PQR' column_value from dual;

Table created.

ops$tkyte%ORA11GR2> 
ops$tkyte%ORA11GR2> merge into t
  2  using b
  3  on (1=1)
  4  when matched then update set c2 = b.column_value;
using b
      *
ERROR at line 2:
ORA-30926: unable to get a stable set of rows in the source tables


Sending email in tablular format

A reader, December 27, 2014 - 1:10 am UTC

PROCEDURE P_SEND_LOAD_DETAILS(
i_component_name IN VARCHAR2 DEFAULT 'RAM' ,
I_HEADER IN VARCHAR2 ,
I_sQL_STATEMENT IN VARCHAR2 )
IS
ctxh DBMS_XMLGEN.ctxHandle;
queryresult XMLTYPE;
xslt_tranfsorm XMLTYPE;
l_text CLOB;
L_FRM VARCHAR2( 200 ) ;
l_col_count NUMBER;
l_col_descs dbms_sql.desc_tab;
l_cursor NUMBER;
l_table_name VARCHAR2( 50 );
L_ALL_COL_DESC VARCHAR2( 4000 );
L_ALL_COL_STYLE VARCHAR2( 4000 );
BEGIN
-- Define the table name
l_table_name := 'tab';
--
-- Build the SQL statement
l_cursor := dbms_sql.open_cursor;
--
-- Parse the SQL statement
dbms_sql.parse( c => l_cursor , STATEMENT => I_sql_statement , language_flag
=> dbms_sql.native );
--
-- Describe the columns of the cursor
dbms_sql.describe_columns( c => l_cursor , col_cnt => l_col_count , desc_t =>
l_col_descs );
--
-- Output the results: Header
dbms_output.put_line( 'Description of table ' || l_table_name || ':' ) ;
--
-- Output the results: Column names
FOR i IN 1..l_col_count
LOOP
L_ALL_COL_DESC := L_ALL_COL_DESC ||' <th>' || l_col_descs( i ).col_name ||
' </th>'||CHR( 13 );
L_ALL_COL_STYLE := L_ALL_COL_STYLE||
' <td style="text-align:left;"> <xsl:value-of select="'||l_col_descs( i )
.col_name||'"/> </td> '||CHR( 13 );
END LOOP;
-- SQL Query :
ctxh := DBMS_XMLGEN.newContext( I_sqL_statement ) ;
-- XSLT Transformation to HTML :
xslt_tranfsorm := NEW XMLTYPE(
'







<xsl:stylesheet xmlns:xsl=" http://www.w3.org/1999/XSL/Transform" version="1.0">







<xsl:template match="/ROWSET">







<table>







<tr>'
||L_ALL_COL_DESC || '</tr>







<xsl:for-each select="ROW">







<tr>' ||
L_ALL_COL_STYLE||
'







</tr>







</xsl:for-each>







</table>







</xsl:template>







</xsl:stylesheet>'
) ;
queryresult := DBMS_XMLGEN.getXMLType( ctxh ) .transform( xslt_tranfsorm ) ;
L_TEXT := '<h3><center>'||i_header||'</center></h3> ' ||
queryresult.getClobVal( ) ;
FOR C1 IN
(
SELECT
*
FROM
EMAIL_CMPNT_LKUP ,
GLOBAL_NAME
WHERE
COMPONENT_NAME = I_COMPONENT_NAME
)
LOOP
L_FRM := C1.GLOBAL_NAME||'_DATABASE ';
send_mail( L_FRM , C1.EMAIL_ADDRESS , i_header , l_text ) ;
END LOOP;
end p_send_load_details;

I have query on collections if they can be used to replace a table

Mohammed Imran, August 06, 2015 - 2:39 pm UTC

Hi Tom,
I am analyzing a performance issue which includes a function having logic which loops through(collection) table records(around 1 million). In each iteration updates(no inserts/deletes) are done on few other records of the same table based on some validations. I believe multiple select and update statements on the table in loop are cause of performance issue.
1st question, Is it possible to rewrite the code to fetch all table data into a collection and do select’s and update’s on the collection in loop rather than on table?
2nd question, will the performance improve by doing so? Please suggest.

function algorithm.
CREATE OR REPLACE TYPE "TYPE_R" IS OBJECT(col1,col2…);
CREATE OR REPLACE TYPE " type_l " IS TABLE OF TYPE_R;

Function F1()
l_line type_l ;
l_line2 type_l ;
Begin
select bulk collect into l_line from lines_tab; --around 1Million records.
For var_l IN l_line LOOP
select * into l_line2 from TABLE (CAST (l_line2 AS *Table Object)) where col1 < -100 and col2 = :2 and nvl(col3,’~’) = :3 and col4 IN (‘X’,’Y’,’Z’);
--Some Validations
Update table … ---need to figure how to update a collection
End loop;
END;

Awaiting for your response.

Thanks,
Imran.

I have query on collections if they can be used to replace a table

Mohammed Imran, August 07, 2015 - 4:08 am UTC

Sorry I missed to mention about stats in my previous post above. The function algorithm mentioned above is new proposed algorithm and with the existing logic it takes around 75 minutes to process to 1 million records.

DB version - 11.2.0.3.0 - 64bit Production
DB Server OS – Linux.

Explanation with small demo will be more helpful. Thanks.

More to Explore

PL/SQL demos

Check out more PL/SQL tutorials on our LiveSQL tool.

PL/SQL docs

PL/SQL reference manual from the Oracle documentation library