Skip to Main Content
  • Questions
  • Update table with multiple columns from another table ?

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question, Parag Jayant Patankar.

Asked: June 28, 2005 - 9:20 am UTC

Last updated: June 18, 2013 - 3:20 pm UTC

Version: 9.2

Viewed 100K+ times! This question is

You Asked

Hi Tom,

Due to migration to new system we have to change all our account numbers. ( Client number is consist of branch, Number, Sub Number Currency Code )

We have one big transaction table around 1 million records, having many columns, and at many places client numbers are stored from account master under various columns

1. Client
2. Purchase Supplier ( may have all null values )
3. Sales Supplier ( may have all null values )
4. Wholesaler
5. Retalier ( may have all null values )
..
etc

I have a mapping table where "ALL" old account number and new account numbers are present.

I have to update a transaction table where mapping is existing with old account no and new account number.

I want to write SQL ( not PL/SQL ) to update transaction table from mapping table at one stroke.

Can you show me how to write such SQL ?

regards & thanks
pjp

and Tom said...

update ( select old.old_account_number, new.new_account_number
from old_table old,
mapping_table new
where old.old_account_number = new.new_account_number )
set old_account_number = new_account_number;


disabling foreign keys during the operation and enabling them afterward.

Rating

  (67 ratings)

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

Comments

Correction ?

Gary, June 28, 2005 - 8:02 pm UTC

Don't you mean....

update ( select old.old_account_number, new.new_account_number
from old_table old,
mapping_table new
where old.old_account_number = new.old_account_number )
set old_account_number = new_account_number;

(ie in the subquery, match the old account numbers not try matching the old in the old table to the new in the mapping table)

Of course if the original posting had the CREATEs/INSERTS etc, it would have a quick and east test.

Tom Kyte
June 28, 2005 - 8:46 pm UTC

yes, the predicate in the join for the fictional question without a sample table and data was wrong

(everytime I wing it, try to answer WITHOUT TESTING, it comes out wrong. That means something doesn't it....... test cases are relevant :)

A reader, June 29, 2005 - 2:02 am UTC

Hi Tom,

Thanks for answering my question. You told me to use

update ( select old.old_account_number, new.new_account_number
from old_table old,
mapping_table new
where old.old_account_number = new.old_account_number )
set old_account_number = new_account_number;

But I think my questions still reamin unanswered

1. How to update multiple columns in single SQL ? In your answer only one number say client number will get updated. But Sales Supplier, Purchase supplier, Wholesale Supplier ...etc many other columns how can i update in single SQL ( I have to update around 70 columns and some columns may have null values such as Purchase supplier ...etc )

2. Secondly you have shown me to update table with "KEY PRESERVED" method. If it is not possible can you show me how to write "GOOD" sql ?

regards & thanks
pjp

Tom Kyte
June 29, 2005 - 8:53 am UTC

select as many old columns as you want and as many new columns as you want???


update ( select old.a, old.b, old.c, ....
new.x, new.y, new.z, ....
from ...
)
set a = x, b = y, c = z, ..............



You need to have the table you are updating from have a unique or primary key constraint. IT NEEDS THIS LOGICALLY regardless of the method used, so just add it (for if a row in OLD joins to more than one row in NEW -- you are hosed, you cannot update this data, therefore NEW has a unique/primary key, add it to facilitate the update)

Update

chithra, September 28, 2005 - 9:46 am UTC

I had a similiar query and i tried applying to my tables. i.e, one column of a table should get updated from another column of another table. But when i did the same with the syntax given i got this error ora-01779 cannot modify a column which maps to a non key preserved value.
Can you please clarify.

Tom Kyte
September 28, 2005 - 10:29 am UTC

you need to have a unique/primary key constraint on the table you are joining to.


ops$tkyte@ORA10G> create table t1 ( x int, y int );
 
Table created.
 
ops$tkyte@ORA10G> create table t2 ( a int, b int );
 
Table created.
 
ops$tkyte@ORA10G>
ops$tkyte@ORA10G> update ( select * from t1, t2 where t1.x = t2.a )
  2    set y = b;
  set y = b
      *
ERROR at line 2:
ORA-01779: cannot modify a column which maps to a non key-preserved table
 
 
ops$tkyte@ORA10G> alter table t2 add constraint t2_pk primary key(a);
 
Table altered.
 
ops$tkyte@ORA10G> update ( select * from t1, t2 where t1.x = t2.a )
  2    set y = b;
 
0 rows updated.


The database wants to know that each row in T1 joins to AT MOST one row in T2. 

Tapan Pandya, January 09, 2006 - 2:43 am UTC


More information

Anurag Doshi, March 28, 2006 - 4:46 pm UTC

If you are 2 tables owned by different users, you will need update rights on both the tables even when you are updating only 1 table.

Did not updated my table

Nabeel Khan, April 24, 2008 - 5:13 am UTC

I tried doing the same through this

update ( select old.IDENTIFICATIONNUMBER,
old.CONTACTNO1,
old.CONTACTNO2,
old.CONTACTNO3,
old.CONTACTNO4,
old.CONTACTNO5,
new.IDENTIFICATIONNUMBER,
new.MOBILEPHONE1,
new.MOBILEPHONE2,
new.HOMEPHONE,
new.FAXNUMBER,
new.WORKEXTENSION
from TBL_VOTERS old,
VOTERSWITHNUMBERS new
where old.MOINUMBER = new.MOINUMBER )
set
old.IDENTIFICATIONNUMBER = new.IDENTIFICATIONNUMBER,
old.CONTACTNO1 = new.MOBILEPHONE1,
old.CONTACTNO2 = new.MOBILEPHONE2,
old.CONTACTNO3 = new.HOMEPHONE,
old.CONTACTNO4 = new.FAXNUMBER
old.CONTACTNO5 = new.WORKEXTENSION;

But I was getting following error, by the Im on 10g R2
SQL Error: ORA-00904: "NEW"."WORKEXTENSION": invalid identifier
00904. 00000 - "%s: invalid identifier"
Tom Kyte
April 28, 2008 - 11:23 am UTC

you do not use correlation names "up", only "down" in a query (eg: down into a correlated subquery)

update ( select old.IDENTIFICATIONNUMBER old_id, 
old.CONTACTNO1 old_c1,
old.CONTACTNO2 old_c2,
old.CONTACTNO3 ....., 
new.IDENTIFICATIONNUMBER new_id,
new.MOBILEPHONE1 new_m1,
new.MOBILEPHONE2 ...
     from TBL_VOTERS old,
             VOTERSWITHNUMBERS new
  where old.MOINUMBER = new.MOINUMBER )
  set
  old_id = new_id, .....


Did not updated my table followup

Nabeel Khan, April 24, 2008 - 5:15 am UTC

There is a comma after the second last column in set

Updating rows in table1 matching some rows in table2

Ovidiu, May 05, 2008 - 7:26 am UTC

I use this statement:
update table1 set col1 = col2 + col3
where col4 = 'OK'
and exists (select distinct 1 from table2 where table1.col_x = table2.col_x2 and table2.col_y2 = 'superOK')

This update works, but is too slow...
table1 is huge and table2 is small.
1 row in table1 matches more rows in table2.

Using join in select statement is very fast!
select col2 + col3 as result
from table1
inner join (select distinct col_x2 from table2 where col_y2 = 'superOK') temp on temp.col_x2 = col_x1
where col4 = 'OK'

How can I use join in my update statement?
I think it will work faster.
Thanks!
Tom Kyte
May 05, 2008 - 10:23 am UTC

what about

update table1 set col1 = col2 + col3
where col4 = 'OK'
and col_x in (select col_x2 from table2 col_y2 = 'superOK') 

update

A reader, May 05, 2008 - 2:11 pm UTC

books
------
bkno PK
bk_flag

ORDERED_ITEMS
--------
order_date <PK>
cust_id <PK>
bkno <PK>
qty


I want to update ORDERED_ITEMS table for a few records but exclude any book where BOOKS.bk_flag='X'

Would you do this

UPDATE ordered_item SET
qty = new_qty WHERE order_date = xxxx and cust_id = xxxx
and bkno not in (select bkno from books where bk_flag <> X)

or would you do an update on a JOIN.
Tom Kyte
May 06, 2008 - 12:42 am UTC

tell me how you would do an update on a join here?

you update a join when you want the data from T1 to overwrite what is in T2 and you join T2 to T1 by T1's primary key.

Here you want to set a qty to a constant for some records - and you 'anti-join' to the other table, not join.

this is 'correct' as it is.

Update Table

Dawar, May 05, 2008 - 7:05 pm UTC

SQL> create table jobs
2 (job_no number(4),
3 title varchar2(50),
4 rate_type varchar2(1),
5 flat_rate_amt number(8,2),
6 max_salary number(8,2));

SQL> create table class_test
2 (JOB_NO NUMBER(4),
3 MIN_RANGE VARCHAR2(14),
4 MAX_RANGE_CNTRL VARCHAR2(14) );

SQL> create table types
2 (type varchar2(1),
3 description varchar2(50),
4 factor number(4));

************************************************************************
Insert table:

insert into types values ('A','Monthly','12')
/
insert into types values ('B','HOURLY','1796')
/
insert into types values ('G','Daily','224')
/
insert into types values ('H','Per Visit','')
/
insert into types values ('I','Per session','')
/
********************************************************************
insert into class_test values ('0001','3,281.18','4,177.64')
/
insert into class_test values ('0002','3,779.27','4,820.00')
/
insert into class_test values ('0003','4,779.27','5,820.00')
/
insert into class_test values ('0004','5,779.27','6,820.00')
/
insert into class_test values ('0005','7,311.45,'10,402.64')
/
insert into class_test values ('0005','','20.83')
/
insert into class_test values ('0006','','14,528.60')
/
insert into class_test values ('0007','5,001.82','5,727.91')
/
******************************************************************
insert into jobs values ('0001','CSI','A','','50131.68')
/
insert into jobs values ('0002','TTI','A','','200')
/
insert into jobs values ('0003','SSP','A','','')
/
insert into jobs values ('0004','CCC','A','','')
/
insert into jobs values ('0006','CIO','A','','')
/
insert into jobs values ('0007','CIO','A','','')
/

It should be update rows in JOBS table
where rate_type = 'A', 
and ITEM_NO of JOBS table should be match with ITEM_NO of class_test table.

update jobs
set MAXIMUM_ANNUAL_SALARY =TYPES.FACTOR * CLASS_TEST.MAX_RANGE_CNTRL 
where jobs.rate_type = 'A' and 
jobs.ITEM_NO = CLASS_TEST.ITEM_NO
5 ;
jobs.ITEM_NO = CLASS_TEST.ITEM_NO
*
ERROR at line 4:
ORA-00904: "CLASS_TEST"."ITEM_NO": invalid identifier

Any help will be appreciated.


D

Tom Kyte
May 06, 2008 - 1:07 am UTC

and the types table plays into this HOW?

where are your primary keys? You want to join basically three tables together, I can envision that jobs.item_no = class_test.item_no, but see NO WAY to add in TYPES to that join

this question begs more questions than answers itself.

Updating rows in table1 matching some rows in table2

Ovidiu, May 06, 2008 - 5:03 am UTC

Thanks for your input!
The problem was:
table2 (~5000 rows) was not analyzed...

After
analyze table table2 compute statistics
everything is ok.
The original update works very fast now!

Thanks again!

Pedro, May 14, 2008 - 10:22 am UTC

Hello Tom.
I have a problem similar to this, but probably a little simpler, but I can't make it to work
I have 2 tables, one having a subset of columns the other has, i.e.:
Table A
c1 <PK>
c2
c3
c4
c5

Table A_Extended
nc1 <PK>
nc2
nc3
nc4
nc5
nc6

nc1 matches c1, nc2 matches c2, nc3 matches c3

What I need to do is update particular records from one of the tables, but only the common columns

The conceptual SQL should be some in the likes of (this is not a query don't hit me ;) it's what is intended to be done):
Update table_extended
set table_extended.nc2 = table.c2,
table_extended.nc3 = table.c3
where table_extended.nc1 = table.c1 = @parameter

Is there a simple way of doing this update?
Tom Kyte
May 14, 2008 - 3:39 pm UTC

I did not follow this - sorry, not sure what you need.

Pedro, May 15, 2008 - 8:36 am UTC

Ok, going to try and explain better with an example:
Table A
ID <PK>
Name
Marital Status
Age
Number of kids

Table B
ID <PK>
Name
Marital Status
Mobile Phone
Email

I want to update table A (Name and Marital Status) using table B data, considering that ID matches in both tables, and using a designated ID (the use of Id may be optional, but for sake of simplicity lets assume we only want to update 1 row, but multiple columns at same time)

Update table A
set table A.Name = table B.Name,
table A.Marital Status = table B.Marital Status
where table A.ID = table B.ID = @parameter

Hope I succeded in making it clearer.
Tom Kyte
May 19, 2008 - 12:03 pm UTC

update ( select a.marital_status a_ms, a.name a_nm, 
                b.marital_status b_ms, b.name b_nm
           from a, b 
          where a.id = b.id )
set a_ms = b_ms, a_nm = b_nm;



*exactly* like the original answer way up on the top of the page.

Just join

Using exists

Tilak, May 28, 2008 - 7:11 am UTC

If i am updating & using non key preserved tables
then exists seems to be quicker one!
But if i update millions is there any other way ?

update account_allocation2
set (sup_hyg_acc_policy,sup_hyg_acc_add)=
( select sup_hyg_acc_policy,sup_hyg_acc_add
from acc_hyg_driver where acc_hyg_driver.account_number =
account_allocation2.account_number
and exists ( select '1' from acc_hyg_driver where acc_hyg_driver.account_number =
account_allocation2.account_number ))

any ideas?? to make it quicker!
Tom Kyte
May 28, 2008 - 9:45 am UTC

merge into account_allocation2 
using acc_hyg_driver
on (acc_hyg_driver.account_number = 
   account_allocation2.account_number)
when matched 
then update set sup_hyg_acc_policy = acc_hyg_driver.sup_hyg_acc_policy, 
            sup_hyg_acc_add = acc_hyg_driver.sup_hyg_acc_add



if you are on 9i, you'll need:

merge into account_allocation2 <b>
using (select * from acc_hyg_driver where account_number in (select account_number from account_allocation2) acc_hyg_driver</b>
on (acc_hyg_driver.account_number = 
   account_allocation2.account_number)
when matched 
then update set sup_hyg_acc_policy = acc_hyg_driver.sup_hyg_acc_policy, 
            sup_hyg_acc_add = acc_hyg_driver.sup_hyg_acc_add
<b>when not matched then insert (account_number) values ( null )</b>



the "using" query only needs to use the IN if acc_hyg_driver might have account numbers that are NOT IN account_allocation2, if acc_hyp_driver only has account numbers in that set, you can just use that table.

Tilak, May 29, 2008 - 11:46 am UTC

Excellent!
But the volumes dealt with are
account_allocation has 16 million
acc_hyg_driver has 6 million
and the update does around 6 million records,

The current merge takes more than 5 hours with other bottle necks ( includes CPU sharing) which cant be helped!

Is it better to go for Create Table as Select (update info) ????

Many thanks,


Tom Kyte
May 29, 2008 - 1:10 pm UTC

and if the other bottlenecks exist for merge, they shall exist for create table as select - plus reindex - plus grant - plus constrain

what are your waits for, got a tkprof with waits?

Tilak, June 04, 2008 - 11:59 am UTC

Create Table works quicker! (no over heads for that!)
But i cant use the create for limitations on downtime of the table.
Dont want the table to be dropped and recreated.
Unfortunately i cant get the tkprof as i dont have access to.
Is there any other way i can use to speed up the update by any efficient means!
Interestingly the Update that is suggested by you takes 12 hours and Update using the PL/SQL cursor style takes 8 hrs
consistently on the live environment!
any suggestions or code that i can try?

thanks in advance
Tom Kyte
June 04, 2008 - 12:03 pm UTC

create table has to be followed by indexing, grants, constraints and everything else - have you accounted for THAT.


...
Unfortunately i cant get the tkprof as i dont have access to.
....

if you were asked to pilot a plane with a blindfold on - how well do you think you would do.

no inputs = no outputs.

I cannot tell you how to make something more efficient if I do not know what is impeding the efficiency in the first place.

another update

A reader, September 25, 2008 - 9:01 pm UTC

Tom,

Is there workaround for the following?

Thank you.

SQL> create table t1 (x int, y int);

Table created.

SQL> alter table t1 add constraint t1_pk primary key (x);

Table altered.

SQL> drop table t2;

Table dropped.

SQL> create table t2 (a int, b int);

Table created.

SQL> alter table t2 add constraint t2_pk primary key (a,b);

Table altered.

SQL> update ( select * from t1, t2 where t1.x = t2.a )
  2      set y = b;
    set y = b
        *
ERROR at line 2:
ORA-01779: cannot modify a column which maps to a non key-preserved table

Tom Kyte
September 26, 2008 - 1:11 pm UTC

you can use merge

merge into t1
using t2
on (t1.x = t2.a)
when matched then update set y = t2.b;

if there exist duplicated by t2.a (more than one row with t2.a having the same value), the merge will fail at runtime when it encounters you trying to modify Y in a non-deterministic fashion.


ops$tkyte%ORA10GR2> create table t1 ( x int, y int );

Table created.

ops$tkyte%ORA10GR2> create table t2 ( a int, b int );

Table created.

ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> insert into t1 values ( 1, null );

1 row created.

ops$tkyte%ORA10GR2> insert into t2 values ( 1, 2 );

1 row created.

ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> merge into t1
  2  using t2
  3  on (t1.x = t2.a)
  4  when matched then update set y = t2.b;

1 row merged.

ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> insert into t2 values ( 1, 3 );

1 row created.

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


Update from other table

geoff, October 06, 2008 - 12:49 am UTC

Dear Tom,

You mentioned previously that we need to have the table we are updating from to have a unique or primary key constraint. What if i need to update a table using data from an external table?The way i understand it external tables don't have primary keys. I don't think i can use the merge statement here since there are more than 1 condition that has to be met for the target table to be updated. Any ideas? Thanks very much!
Tom Kyte
October 06, 2008 - 2:50 pm UTC

use merge

merge into real_table
using external_table
on (join condition)
when matched then update.....;


Update using external tables

geoff, October 06, 2008 - 11:24 pm UTC

Hi Tom!
thank you very much for the quick response!I have followup questions:

1.Can we use the WHERE clause in the merge statement? my oracle version is 9i.

2.Can we put several conditions in the ON clause?

And why do i get an 'invalid column name' error when i run this?

UPDATE ORD_DTL D1
SET
D1.OD_ITEM_TYPE=EXT_URIN_DTL_RESULT.OD_ITEM_TYPE
D1.OD_ITEM_PARENT=EXT_URIN_DTL_RESULT.OD_ITEM_PARENT
D1.OD_ORDER_TI=EXT_URIN_DTL_RESULT.OD_ORDER_TI
D1.OD_PKG_ORDER=EXT_URIN_DTL_RESULT.OD_PKG_ORDER
D1.OD_SEQ_NO=EXT_URIN_DTL_RESULT.OD_SEQ_NO
D1.OD_TR_VAL=EXT_URIN_DTL_RESULT.OD_TR_VAL
D1.OD_TR_UNIT=EXT_URIN_DTL_RESULT.OD_TR_UNIT
D1.OD_TR_FLAG=EXT_URIN_DTL_RESULT.OD_TR_FLAG
D1.OD_TR_RANGE=EXT_URIN_DTL_RESULT.OD_TR_RANGE
D1.OD_NORMAL_LOLIMIT=EXT_URIN_DTL_RESULT.OD_NORMAL_LOLIMIT
D1.OD_NORMAL_UPLIMIT=EXT_URIN_DTL_RESULT.OD_NORMAL_UPLIMIT
D1.OD_PANIC_LOLIMIT=EXT_URIN_DTL_RESULT.OD_PANIC_LOLIMIT
D1.OD_PANIC_UPLIMIT=EXT_URIN_DTL_RESULT.OD_PANIC_UPLIMIT
D1.OD_TR_COMMENT=EXT_URIN_DTL_RESULT.OD_TR_COMMENT
D1.OD_01_VAL=EXT_URIN_DTL_RESULT.OD_01_VAL
D1.OD_02_VAL=EXT_URIN_DTL_RESULT.OD_02_VAL
D1.OD_03_VAL=EXT_URIN_DTL_RESULT.OD_03_VAL
D1.OD_TEST_GRP=EXT_URIN_DTL_RESULT.OD_TEST_GRP
D1.OD_SPL_TYPE=EXT_URIN_DTL_RESULT.OD_SPL_TYPE
D1.OD_DATA_TYPE=EXT_URIN_DTL_RESULT.OD_DATA_TYPE
D1.OD_ORDER_ITEM=EXT_URIN_DTL_RESULT.OD_ORDER_ITEM
D1.OD_ANZ_ORDER=EXT_URIN_DTL_RESULT.OD_ANZ_ORDER
D1.OD_WC_CODE=EXT_URIN_DTL_RESULT.OD_WC_CODE
D1.OD_LOGNO=EXT_URIN_DTL_RESULT.OD_LOGNO
D1.OD_RESULT_SRC=EXT_URIN_DTL_RESULT.OD_RESULT_SRC
D1.OD_ACTION_FLAG=EXT_URIN_DTL_RESULT.OD_ACTION_FLAG
D1.OD_EDIT_FLAG=EXT_URIN_DTL_RESULT.OD_EDIT_FLAG
D1.OD_STATUS=EXT_URIN_DTL_RESULT.OD_STATUS
D1.OD_VALIDATE_BY=EXT_URIN_DTL_RESULT.OD_VALIDATE_BY
D1.OD_VALIDATE_ON=EXT_URIN_DTL_RESULT.OD_VALIDATE_ON
D1.OD_ANZ_ID=EXT_URIN_DTL_RESULT.OD_ANZ_ID
D1.OD_ANZ_RACKNO=EXT_URIN_DTL_RESULT.OD_ANZ_RACKNO
D1.OD_ANZ_TUBENO=EXT_URIN_DTL_RESULT.OD_ANZ_TUBENO
D1.OD_UPDATE_BY=EXT_URIN_DTL_RESULT.OD_UPDATE_BY
D1.OD_UPDATE_ON=EXT_URIN_DTL_RESULT.OD_UPDATE_ON
D1.OD_ANZ_COMMENT=EXT_URIN_DTL_RESULT.OD_ANZ_COMMENT
D1.OD_CTL_FLAG1=EXT_URIN_DTL_RESULT.OD_CTL_FLAG1
D1.OD_CTL_FLAG2=EXT_URIN_DTL_RESULT.OD_CTL_FLAG2
WHERE D1.OD_TNO=EXT_URIN_DTL_RESULT.OD_TNO
AND D1.OD_TESTCODE=EXT_URIN_DTL_RESULT.OD_TESTCODE
AND D1.OD_CTL_FLAG2 IS NULL
AND EXT_URIN_DTL_RESULT.OD_ACTION_FLAG = 'R';

this is related to my question, this is why i did not use MERGE because i have several conditions for the update.Error points to line 42,invalid column name, even if the column does exist (EXTERNAL TABLE)and i did not misspell it either :).

THANKS!
Tom Kyte
October 07, 2008 - 1:17 pm UTC

1) where do you want to use this where clause.

update t
using (select * from t2 where ... )
on (joins)


2) the documentation would be very telling wouldn't it? The merge statement is fully documented... (answer = yes, you can)


as for the last one - because you have an invalid column name?? I don't know, I don't have *your tables*

whittle your example down, if it is on line 42, get rid of 90% of the lines before line 42 - then add them back in until it fails again - then you'll find your syntax error or mistake.

Dan, October 08, 2008 - 10:40 am UTC

"this is related to my question, this is why i did not use MERGE because i have several conditions
for the update.Error points to line 42,invalid column name, even if the column does exist (EXTERNAL
TABLE)and i did not misspell it either :)."

You aren't using commas in your update, Oracle thinks you are updating just one thing and isn't recognizing the massive block.

UPDATE ORD_DTL D1
SET
D1.OD_ITEM_TYPE=EXT_URIN_DTL_RESULT.OD_ITEM_TYPE
D1.OD_ITEM_PARENT=EXT_URIN_DTL_RESULT.OD_ITEM_PARENT
D1.OD_ORDER_TI=EXT_URIN_DTL_RESULT.OD_ORDER_TI
...

Should be:
UPDATE ORD_DTL D1
SET
D1.OD_ITEM_TYPE=EXT_URIN_DTL_RESULT.OD_ITEM_TYPE,
D1.OD_ITEM_PARENT=EXT_URIN_DTL_RESULT.OD_ITEM_PARENT,
D1.OD_ORDER_TI=EXT_URIN_DTL_RESULT.OD_ORDER_TI,
...

Tom Kyte
October 08, 2008 - 10:14 pm UTC

thanks, to be honest, if I cannot run it in sqlplus these days myself - I don't even look at it.

It someone does not take a couple of minutes to develop a small test case - I'm not sure why I should spend minutes compiling their code in my head anymore :)

funny thing, if they would have done this:

whittle your example down, if it is on line 42, get rid of 90% of the lines before line 42 - then add them back in until it fails again - then you'll find your syntax error or mistake.

which is what I would have to do - they would have discovered this all by themselves...

something I don't understand

Piotr, November 03, 2008 - 2:20 pm UTC

Hello Tom,

I've been using join updates for some time, but recently
I run into an unexplained behavior from Oracle. Here is
a short demonstration (I'm using 10.2.0.4 on Windows)


create table x3 (
id number ,
val number,
constraint pk_x3 primary key (id)
);

create table x4 (
id number,
val number,
constraint pk_x4 primary key (id)
);

insert into x3 values (1,55);
insert into x3 values (2,56);
insert into x4 values (1,57);
insert into x4 values (2,58);

-- this works fine:
update (
select x3.val, x4.val new_val
from x3 inner join x4 on x3.id=x4.id
) set val=new_val;

> 2 rows updated

-- but this produces an error:
update (
select x3.val, x4.val new_val
from x3 inner join x4 on x3.id=1 and x4.id=1
) set val=new_val;


> SQL Error: ORA-01779: cannot modify a column which maps to a non key-preserved table


Is there any reason for that? Does it mean, that in order
to be able to use join updates, we need to build the join
conditions only based on the values in both tables, and
cannot use directly provided values?

You may ask for a real-world scenario to use such syntax.
In my case, I have a table that contains items that belong
to a certain class, and the table has a PK based on two
columns: (CLASS_ID, ITEM_CODE).
Then I want to update rows with the same ITEM_CODE from
one class, with rows of a matching ITEM_CODE, but from
a different class, so the statement looks like this:

update (
select ...
from items c1 inner join items c2
on c1.item_code=c2.item_code
and c1.class_id= <value1>
and c2.class_id= <value2>
) set ....

Which produces the ORA-01779 error for exactly the same
reasons, as in the simplified example above.

I have a workaround, so this is not a big problem, but I'm just wondering if there is any reason for such situation?


Best regards,

Piotr
Tom Kyte
November 11, 2008 - 11:43 am UTC

http://docs.oracle.com/docs/cd/B19306_01/server.102/b14231/views.htm#sthref3058

it has always been a restriction of the join update - you need to join the table you are updating (t1) to another table (t2) by the primary key/unique constraint defined on t2.

ops$tkyte%ORA10GR2> create table t1(x int, y int);

Table created.

ops$tkyte%ORA10GR2> create table t2(a int, b int );

Table created.

ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> update(select * from t1,t2 where t1.x = t2.a) set y = b;
update(select * from t1,t2 where t1.x = t2.a) set y = b
                                                  *
ERROR at line 1:
ORA-01779: cannot modify a column which maps to a non key-preserved table


ops$tkyte%ORA10GR2> alter table t2 add constraint t2_pk primary key(a);

Table altered.

ops$tkyte%ORA10GR2> update(select * from t1,t2 where t1.x = t2.a) set y = b;

0 rows updated.

ops$tkyte%ORA10GR2> alter table t2 drop constraint t2_pk;

Table altered.

ops$tkyte%ORA10GR2> merge into t1 using t2 on (t1.x = t2.a)
  2  when matched then update set y = b;

0 rows merged.


as demonstrated, Merge has no such restrictions.

Updating multiple colum

A reader, November 20, 2008 - 7:40 am UTC

Update TableName A
SET (FLD1,FDL2,FLD3) = (Select FLD1,FLD2,FLD3 from TableX),
FLD4 = 'X'
WHere
Exists (Select 'X' from TableNAme B)


if the following query return Zero rows
Select FLD1,FLD2,FLD3 from TableX
I need to use another query

Select FLD1,FLD2,FLD3 from TableABC

How can incorporate this in Update statement.

I tried using Coalesce but it didn't work.....



Tom Kyte
November 24, 2008 - 12:59 pm UTC


ops$tkyte%ORA10GR2> create table t1 ( key int primary key, x int, y int, z int );

Table created.

ops$tkyte%ORA10GR2> create table t2 ( key int primary key, x int, y int, z int );

Table created.

ops$tkyte%ORA10GR2> create table t3 ( key int primary key, x int, y int, z int );

Table created.

ops$tkyte%ORA10GR2> exec dbms_stats.set_table_stats( user, 'T1', numrows => 1000000 );

PL/SQL procedure successfully completed.

ops$tkyte%ORA10GR2> exec dbms_stats.set_table_stats( user, 'T2', numrows => 1000000 );

PL/SQL procedure successfully completed.

ops$tkyte%ORA10GR2> exec dbms_stats.set_table_stats( user, 'T3', numrows => 1000000 );

PL/SQL procedure successfully completed.

ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> insert into t1 select rownum, 100, 100, 100 from all_users where rownum <= 6;

6 rows created.

ops$tkyte%ORA10GR2> insert into t2 select rownum, rownum, rownum, rownum from all_users where rownum <= 3;

3 rows created.

ops$tkyte%ORA10GR2> insert into t3 select rownum+2, rownum+20, rownum+20, rownum+20 from all_users where rownum <= 3;

3 rows created.

ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> select * from t1;

       KEY          X          Y          Z
---------- ---------- ---------- ----------
         1        100        100        100
         2        100        100        100
         3        100        100        100
         4        100        100        100
         5        100        100        100
         6        100        100        100

6 rows selected.

ops$tkyte%ORA10GR2> select * from t2;

       KEY          X          Y          Z
---------- ---------- ---------- ----------
         1          1          1          1
         2          2          2          2
         3          3          3          3

ops$tkyte%ORA10GR2> select * from t3;

       KEY          X          Y          Z
---------- ---------- ---------- ----------
         3         21         21         21
         4         22         22         22
         5         23         23         23

ops$tkyte%ORA10GR2> update t1
  2  set (x,y,z) = (select x,y,z from t2 where t2.key = t1.key
  3                 union all
  4                 select x,y,z from t3 where t3.key = t1.key
  5                                and not exists (select null from t2 where t2.key = t1.key)
  6                            )
  7  where exists  (select x,y,z from t2 where t2.key = t1.key
  8                 union all
  9                 select x,y,z from t3 where t3.key = t1.key
 10                                and not exists (select null from t2 where t2.key = t1.key)
 11                            )
 12  /

5 rows updated.

ops$tkyte%ORA10GR2> select * from t1;

       KEY          X          Y          Z
---------- ---------- ---------- ----------
         1          1          1          1
         2          2          2          2
         3          3          3          3
         4         22         22         22
         5         23         23         23
         6        100        100        100

6 rows selected.



Multiple Column Update based on indivisual conditions

A Reader, April 29, 2009 - 6:42 am UTC

Hi Tom,

Can multiple columns be updated based on different conditions,we have some Type - I columns which needs to be modified if they have changed wrt. to the Delta.

D represents a table
D_DELTA has the changes

X and Y are the Type - I changes, and we need to update based on the WHERE CLAUSE

(a.X <> b.X or ((a.x is null and b.x is not null) or (a.x is not null and b.x is null)))

MERGE INTO D a
USING D_Delta b
ON (a.id = b.id)
When Matched THEN
Update Set a.X = b.x WHERE (a.X <> b.X or ((a.x is null and b.x is not null) or (a.x is not null and b.x is null))),
a.y = b.y WHERE (a.Y <> b.Y or ((a.y is null and b.y is not null) or (a.y is not null and b.y is null)))

It is now working

Any insight how to solve it will be appreciated
Tom Kyte
April 29, 2009 - 9:47 am UTC

"type - I columns"
"wrt"

??

but.... You cannot conditionally update, you either will update a column in a given update/merge statement or you will not.

The approach would be:

merge into d
using d_delta
on (d.id = d_delta.id)
when matched then update set d.x = d_delta.x, d.y = d_delta.y;


The only other option would be *two* merge/update statements. The savinging in redo generated is unlikely to be worth the extra computing resources to run the statement twice.



Update a table from a table with which it has a many to many relation

Rish G, April 30, 2009 - 2:04 am UTC

Hi Tom,

create table stg_emp (
empno varchar2(6),
request_id number,
event_id number,
start_date date
);

create table emp (
empno varchar2(6),
request_id number,
event_id number,
start_date date,
rec_status varchar2(10)
);



insert into stg_emp
values('ab0001', 123400, 1, to_date('05/01/2009', 'MM/DD/YYYY'));
insert into stg_emp
values('ab0001', 678900, 1, to_date('05/15/2009', 'MM/DD/YYYY'));
insert into stg_emp
values('ab0001', 432100, 1, to_date('06/01/2009', 'MM/DD/YYYY'));
insert into stg_emp
values('ab0001', 987600,2, to_date('06/05/2009', 'MM/DD/YYYY'));
insert into stg_emp
values('ab0001', 342100, 1, to_date('05/20/2009', 'MM/DD/YYYY'));
insert into stg_emp
values('ab0006', 879600, 1, to_date('05/01/2009', 'MM/DD/YYYY'));


insert into emp
values('ab0001', 123400, 1, to_date('05/01/2009', 'MM/DD/YYYY'), 'UNKNOWN');
insert into emp
values('ab0001', 345600, 1, to_date('05/10/2009', 'MM/DD/YYYY'), 'UNKNOWN');
insert into emp
values('ab0001', 342100, 1, to_date('05/20/2009', 'MM/DD/YYYY'), 'UNKNOWN');


Data in the stg_emp is a truncate/insert process from a different source. We have no control over this part.

Constraints
empno + request_id = unique. ( many employees can be assigned to one request_id)
request_id is unique to an event_id. The same request_id cannot belong to different event_ids. One event_id can contain many request_ids.

Here is the requirement
We need to compare the emp table with the stg_emp table and do the following.
1. if empno + request_id + event_id is a match, update the rec_status in emp to NO CHANGE.
2. If only the empno + event_id match and the request_id does not match, and the number of records per empno per event_id is the same in both the tables, then update the table with the data from stg_emp and set the rec_status to UPDATED
3. If the number of records per empno per event_id in emp_stg exceeds the number of records per empno per event_id in emp table, then first update the emp table with the stg_table and set the rec_status to update, followed by an insert into emp from emp_stg;
4. If the number of records per empno per event in emp_stg is less than the number of records per empno per event_id in emp table, then do nothing.
For example :
Records for empno ab0001 with event_id = 1 in emp
ab0001, 123400, 1, 05/01/2009, UNKNOWN
ab0001, 345600, 1, 05/10/2009, UNKNOWN
ab0001, 342100, 1, 05/20/2009, UNKNOWN

records for empno ab0001 with event_id = 1 in stg_emp
ab0001, 123400, 1, 05/01/2009
ab0001, 678900, 1, 05/15/2009
ab0001, 342100, 1, 05/20/2009
ab0001, 432100, 1, 06/01/2009


After the update/insert into emp, the data for empno ab0001 for event_id = 1 should look like this :
ab0001, 123400, 1, 05/01/2009, NO CHANGE
ab0001, 678900, 1, 05/15/2009, UPDATED -- > updated with record from emp_stg
ab0001, 342100, 1, 05/20/2009, NO CHANGE
ab0001, 432100, 1, 06/01/2009, NEW

The entire emp table would look like
ab0001, 123400, 1, 05/01/2009, NO CHANGE
ab0001, 678900, 1, 05/15/2009, UPDATED -- > updated with record from stg_emp
ab0001, 342100, 1, 05/20/2009, NO CHANGE
ab0001, 432100, 1, 06/01/2009, NEW
ab0001, 987600,2,06/05/2009, NEW
ab0006, 123400,1, 05/01/2009, NEW

When the possibility of an insert and an update exists simultaneously, update will be performed on the record with the earlier start date.
As you can see there is no unique way to identify a record to determine which one is to be updated and which is to be considered 'new'.

Requirement 1. is easy to implement :
update emp e
set rec_status = (select 'NO CHANGE'
from emp_stage es
where e.empno = es.empno
and e.request_id = es.request_id
and e.event_id = es.event_id)
where e.empno||e.request_id||e.event_id in (select empno||request_id||event_id from emp_stg);


Requirement 2 and 3 is where the problem is. There is no way to determine a 'new' record vs. an 'updated' record. We cannot determine which record to use from the stg_emp table as an update vs. a new record. Unfortunately, the source system was designed in a different way that works for them. This is what we have to work with and there is no way to set up an identifier in the staging table that can indicate an updated vs. a new record. That is beyond our control.
We cannot set up a primary key on our emp table. Although the combination of empno + request_id is always going to be unique, the request_id associated with an empno is not static because of the said requirement.
Is there a way to do it via a single sql statement ? I tried this update which I now know does not work :

update (
select e_empno, e_event_id, e_reqid, e_recstatus, es_empno, es_event_id, es_reqid
from (
select es.empno es_empno, es.request_id es_reqid, es.event_id es_event_id,
row_number() over (partition by es.empno, es.event_id order by es.start_date) es_rn
from STG_EMP ES
where not exists (select 'x' from emp e1 where e1.empno = es.empno
and e1.event_id = es.event_id
and e1.request_id = es.request_id )
) es,
( select e.empno e_empno, e.request_id e_reqid, e.event_id e_event_id, e.rec_status e_recstatus,
row_number() over (partition by e.empno, e.event_id order by e.start_date) e_rn
from emp E where e.rec_status <> 'NO CHANGE') e
where e_empno = es_empno
and e_event_id = es_event_id
and es_rn= e_rn)
set e_reqid = e_reqid,
e_recstatus = 'UPDATED'

Can you suggest any alternatives? A series of sql statements instead of one single statement will also work. Actually anything that can make this work will be great.

Thanks in advance.

Rish G.



update

A reader, May 22, 2009 - 3:31 pm UTC

Tom:

I replicated your example above for merge in 9iR2 and i got an error. o you know why

1 merge into t1
2 using t2
3 on(t1.x=t2.a)
4* when matched then
update set t1.y = t2.b

* ERROR at line 4:ORA-00905: missing keyword
Tom Kyte
May 23, 2009 - 10:34 pm UTC

yes, in 9i you had to have a when matched AND a when not matched.


search for

if you are on 9i

on this page for further discussion

update

sam, May 24, 2009 - 6:57 pm UTC

Tom:

 
I did it this way and it seems to work fine too. any issues with this


  
  create table t1 ( x int, y int )
  /
  create table t2 ( a int, b int )
  /
  insert into t1 values ( 1, null )
  /
  insert into t1 values ( 2,null)
  /
  insert into t1 values (3,null)
  /
  insert into t2 values ( 1, 2 )
  /
  insert into t2 values (2,3)
  /
  insert into t2 values (3,4)
  /
  SQL> select * from t1;
  
           X          Y
  ---------- ----------
           1
           2
           3
  
  SQL> select * from t2;
  
           A          B
  ---------- ----------
           1          2
           2          3
           3          4
  
  SQL> Update t1
    2    set t1.y = (select t2.b from t2 where t1.x=t2.a);
  
  3 rows updated.
  
  SQL> select * from t1;
  
           X          Y
  ---------- ----------
           1          2
           2          3
           3          4

Tom Kyte
May 26, 2009 - 8:44 am UTC

well, it is different than updating a join isn't it.

do you see the difference?

consider what happens if you

insert into t1 (x,y) values ( 4,100 );


if you update a join, you'll get one answer, if you use the correlated update you have - you'll get a different one

so you tell us - which is correct for you? That is what it all comes down to.

update

A reader, May 26, 2009 - 1:03 pm UTC

Tom:

I see your point. In my case T2 is the master table and every a values in T2 must have a value in T1.

But i see your point. It will set the 100 to NULL if it could not find a match.

so i did this and it works and it will give you same result. I can't updaet a join because table does not have PK on those columns.

SQL>  update t1
  2   set t1.y = (select t2.b from t2 where t1.x=t2.a)
  3   where x in (select a from t2);

3 rows updated.

SQL> select * from t1;

         X          Y
---------- ----------
         1          2
         2          3
         3          4
         4        100

Tom Kyte
May 26, 2009 - 4:00 pm UTC

you can use merge of course.

merge into t1
using t2
on (t1.x = t2.a)
when matched then update set y = t2.b;



and typically people would code:

update t1
set t1.y = (select t2.b from t2 where t1.x=t2.a)
where exists (select t2.b from t2 where t1.x=t2.a);

but yours works as well


you do know there should be a unique or primary key constraint on t2.a - if t2.a is not actually unique - failure is yours.

Merge on copy of the same table do not work

Mahendra, June 26, 2009 - 10:52 am UTC

when i run the following merge it is succesful

merge into tk_account_allocation tble using
( select distinct trim(tk.account_number) account_number,tk.open_channel
from tk_phoenix_channel tk,
tk_account_allocation aa
where
trim(tk.account_number)=trim(aa.account_number) ) qry
on ( trim(tble.account_number)=trim(qry.account_number))
when matched then
update set tble.opening_channel=qry.open_channel
when not matched then
insert (tble.ACCOUNT_NUMBER)
values (99999999999099)

whereas i created table of the same table as

create table tk_account_allocation as select * from account_allocation

if i run on the original table account_allocation it comes up with an error as "inserted value too large for the column" when it is not supposed to go into the not matched condition itself
the only difference on the two tables is indexes , does this make any difference in merge??.

many thanks in advance.

Tom Kyte
June 26, 2009 - 11:27 am UTC

I have no idea what you are doing.

...
whereas i created table of the same table as

create table tk_account_allocation as select * from account_allocation
......

whereas how was it created before when it worked????

please give full examples, this one is so incomplete as to be not useful. give FULL example, showing the error and all.

continued...

Mahendra, June 26, 2009 - 10:54 am UTC

this merge fails

merge into account_allocation tble using
( select distinct trim(tk.account_number) account_number,tk.open_channel
from tk_phoenix_channel tk,
account_allocation aa
where
trim(tk.account_number)=trim(aa.account_number) ) qry
on ( trim(tble.account_number)=trim(qry.account_number))
when matched then
update set tble.opening_channel=qry.open_channel
when not matched then
insert (tble.ACCOUNT_NUMBER)
values (9999999999999999999999999999)


Tom Kyte
June 26, 2009 - 11:27 am UTC

no creates
no inserts
no look - never.

How I got it to work....

Jeff, March 29, 2010 - 12:45 pm UTC

Because the database table 'personnel_hr', which is maintained by HR drops terminated employees, I maintain my own phone book lookup table of only the employees who have badges (pk) stored in the tables of my web applications.

INSERT INTO personnel_copy copy (badge, fname, lname, mname, title)
SELECT badge, fname, lname, mname, title FROM personnel_hr WHERE badge ='112211'


UPDATE personnel_copy copy SET (badge, fname, lname, mname, title) =
(select badge, fname, lname, mname, title FROM personnel_hr
WHERE badge = '112211')
WHERE copy.BADGE ='112211' ";


These are actually SQL statements in java class files and a badge number is passed in instead of hard coding. When something pertaining to an employee is stored against their badge (my primary key) I also copy (or insert) their phonebook info so if they get terminated, I can still figure out who the employee ID belonged to. Its redundant but I see no other way.

update

prabha, April 19, 2010 - 3:24 am UTC

hi,

i want to update emp table
update the sal of 'raja' with higesh paid of grade 2 transfer him to ranis depts and change the mgr to blake.
Tom Kyte
April 19, 2010 - 8:55 am UTC

ok, you have my permission to do that.

seriously though - no create table, no inserts, no look - your text description here is not useful to actually start with.

You give no schema
You explain no data
Is raja a name or a group or what?
what is pay grade 2?
how do you transfer people?

You leave us with more questions than answers.

Simple update table.

Rob B, May 05, 2010 - 7:43 am UTC

This will update multiple columns in one table using data from another table.

update table1 t1
set (x1, x2, x3) = (select x1, x2, x3
from table2 t2
where t1.x = t2.x);

Typically t1.x and t2.x are some sort of key fields.
Tom Kyte
May 06, 2010 - 2:02 pm UTC

that is one way - it updates all rows in the table, setting them to null if they do not exist in the other table.


typically, it would be preferred to do:
update (select t1.x1 t1x2, t1.x2 t1x2, t1.x3 t1x3, 
               t2.x1 t2x2, t2.x2 t2x2, t2.x3 t2x3,
          from t1, t2
         where t1.x = t2.x )
set t1x2 = t2x2, t1x3 = t1x3; 



if your goal was just to update the 'matched' pairs.

Ahmed A.Mageed,Cairo Egypt

Ahmed, June 12, 2010 - 9:23 am UTC

UPDATE INVST_COMPANY set

COMP_LAST_MARKET_PRICE=( SELECT col3 FROM xls01 WHERE INVST_COMPANY.COMP_BLOOMBERG_CODE = xls01.col1),
COMP_LST_MRKT_PRIC_PULL_DT=sysdate,
COMP_LST_MRKT_PRICE_MNUL_PULL='P',
COMP_AMS_52_WKS_H_PRIC=( SELECT COL5 FROM xls01 WHERE INVST_COMPANY.COMP_BLOOMBERG_CODE = xls01.col1),
COMP_AMS_52_WKS_H_PR_DT=( SELECT COL6 FROM xls01 WHERE INVST_COMPANY.COMP_BLOOMBERG_CODE = xls01.col1),
COMP_AMS_52_WKS_L_PRIC=( SELECT COL7 FROM xls01 WHERE INVST_COMPANY.COMP_BLOOMBERG_CODE = xls01.col1),
COMP_AMS_52_WKS_L_PR_DT=( SELECT COL8 FROM xls01 WHERE INVST_COMPANY.COMP_BLOOMBERG_CODE = xls01.col1),
COMP_AMS_6MTH_AVG_TRDNG_VOL=( SELECT col9 FROM xls01 WHERE INVST_COMPANY.COMP_BLOOMBERG_CODE = xls01.col1),
COMP_AMS_MARKET_CAP=( SELECT col11 FROM xls01 WHERE INVST_COMPANY.COMP_BLOOMBERG_CODE = xls01.col1),
COMP_AMS_FREE_FLOAT_PCT=( SELECT SUBSTR(col12,1,INSTR(col12,'%')-1) FROM xls01 WHERE INVST_COMPANY.COMP_BLOOMBERG_CODE = xls01.col1),
COMP_AMS_EPS=( SELECT col13 FROM xls01 WHERE INVST_COMPANY.COMP_BLOOMBERG_CODE = xls01.col1),
COMP_AMS_LTM_P_E_RATIO=( SELECT col14 FROM xls01 WHERE INVST_COMPANY.COMP_BLOOMBERG_CODE = xls01.col1),
COMP_AMS_P_BV_RATIO=( SELECT col15 FROM xls01 WHERE INVST_COMPANY.COMP_BLOOMBERG_CODE = xls01.col1),
COMP_AMS_LST_DIVIDEND_PAID=( SELECT col16 FROM xls01 WHERE INVST_COMPANY.COMP_BLOOMBERG_CODE = xls01.col1),
COMP_AMS_DIVIDEND_FREQ=( SELECT col17 FROM xls01 WHERE INVST_COMPANY.COMP_BLOOMBERG_CODE = xls01.col1),
COMP_AMS_DIVIDEND_YIELD=( SELECT SUBSTR(col18,1,INSTR(col18,'%')-1) FROM xls01 WHERE INVST_COMPANY.COMP_BLOOMBERG_CODE = xls01.col1),
COMP_AMS_STOCK_BETA=( SELECT col19 FROM xls01 WHERE INVST_COMPANY.COMP_BLOOMBERG_CODE = xls01.col1),
COMP_AMS_LISTING_EX=( SELECT col20 FROM xls01 WHERE INVST_COMPANY.COMP_BLOOMBERG_CODE = xls01.col1)

WHERE EXISTS
( SELECT xls01.COL1
FROM xls01
WHERE INVST_COMPANY.COMP_BLOOMBERG_CODE= xls01.col1);
Tom Kyte
June 22, 2010 - 7:52 am UTC

not sure what your point is, but you have demonstrated perhaps the slowest way to achieve this update of a join.

was that what you wanted to demonstrate? The slowest approach?

Quite Useful

Ravi Gosai, January 25, 2011 - 7:43 pm UTC

The review was quite useful and It was helpful for me to create my Sql statement for update using foreign key and primary key of two tables.

Thank You author for the post.

Outer join in update

phoenixbai, June 17, 2011 - 5:18 am UTC

Hi tom,

I am trying to do a multiple column update as below:

UPDATE DM_F_LO_JCKKB A
SET (A.BY_JE,A.BY_SWL) =
(SELECT
NVL(DECODE(A.JE,0, A.JE, A.JE-NVL(B.JE,0)),0) AS BY_JE,
nvl(DECODE(A.SWL,0, A.SWL, A.SWL-NVL(B.SWL,0)),0) AS BY_SWL
FROM DM_F_LO_JCKKB B
WHERE B.MONTH_ID(+)=A.MONTH_ID-1
AND A.ORG_ID=B.ORG_ID(+)
AND A.AREA_ID=B.AREA_ID(+)
AND A.ITEM_NAME=B.ITEM_NAME(+)
AND A.SHEET_NAME=B.SHEET_NAME(+));

1) the columns used in the WHERE clause can uniquely identify one row.
2) what it is trying to do is that, trying to update the column BY_JE,BY_SWL of (e.g.) April, using below:
BY_JE(APRIL)=JE(APRIL)-JE(MARCH)
BY_SWL(APRIL)=SWL(APRIL)-SWL(MARCH)

The problem with this SQL is that, when there is no corresponding row for MARCH, it fails to update it to the value of APRIL without any subtraction.

It just set it to NULL. it means the OUTER join is not working. The odd thing is that, it works in the SELECT alone, but it don`t when used in UPDATE.

Do you know why?
is there other way to do it?

Thanks and always been a huge fan!
Tom Kyte
June 17, 2011 - 1:54 pm UTC

that is because when you have an outer join, you need to have TWO tables.

You have but one table in your select. I don't know what you mean by this:

It just set it to NULL. it means the OUTER join is not working. The odd thing
is that, it works in the SELECT alone, but it don`t when used in UPDATE.


You have ONE TABLE in your select, as long as there is ONE TABLE - outer join has no "meaning"

ops$tkyte%ORA11GR2> select * from dual d where 'Y' = d.dummy(+);

no rows selected

ops$tkyte%ORA11GR2> with data as (select 'Y' y from dual)
  2  select * from dual d, data where data.y = d.dummy(+);

D Y
- -
  Y



Your decode is:

nvl(
if a.je = 0
then
return 0
else
return a.je-nvl(b.je)
end if;
, 0 )



If you want me to look at this, I'll need a create and some sample data to play with. I think we need two rows in A and one row in B for a good example.

Outer join in update

PhoenixBai, June 17, 2011 - 9:34 pm UTC

Hi Tom,

CREATE TABLE T (NAME VARCHAR2(10), MONTH_ID NUMBER(6), TOTAL_AMT NUMBER, TOTAL_QTY NUMBER, MONTHLY_AMT NUMBER, MONTHLY_QTY NUMBER);

INSERT INTO T (NAME,MONTH_ID,TOTAL_AMT,TOTAL_QTY) VALUES ('JIMMY',201105,50, 10);
INSERT INTO T (NAME,MONTH_ID,TOTAL_AMT,TOTAL_QTY) VALUES ('JOHN',201103,60, 25);
Insert into T (NAME,MONTH_ID,TOTAL_AMT,TOTAL_QTY) values ('JOHN',201102,50, 15);

I want to set the value for MONTHLY_AMT, MONTHLY_QTY based on the below logic:

MONTHLY_AMT=(TOTAL_AMT OF CURRENT MONTH) - (TOTAL_AMT OF last MONTH);
if there is no record for last month, just set the value of TOTAL_AMT OF CURRENT MONTH to MONTHLY_AMT. Same logic with MONTHLY_QTY.

by using the SELECT, I can get what I want:


SELECT A.NAME, A.MONTH_ID, A.TOTAL_AMT, A.TOTAL_QTY,
NVL((CASE WHEN A.TOTAL_AMT IS NULL THEN NULL
ELSE A.TOTAL_AMT-NVL(B.TOTAL_AMT,0)
END), 0) AS MONTLY_AMT,
NVL((CASE WHEN A.TOTAL_QTY IS NULL THEN NULL
ELSE A.TOTAL_QTY-NVL(B.TOTAL_QTY,0)
END), 0) AS MONTLY_QTY
FROM T B, T A
WHERE A.MONTH_ID-1=B.MONTH_ID(+)
AND A.NAME=B.NAME(+);

name month_id tamt tqty mamt mqty
----------------------------------
JIMMY 201105 50 10 50 10
JOHN 201102 50 15 50 15
JOHN 201103 60 25 10 10

When I try to use the same logic in UPDATE, it is not working:

UPDATE T A
SET (A.MONTHLY_AMT, A.MONTHLY_QTY)=
(SELECT NVL((CASE WHEN A.TOTAL_AMT IS NULL THEN NULL
ELSE A.TOTAL_AMT-NVL(B.TOTAL_AMT,0)
END), 0) AS MONTLY_AMT,
NVL((CASE WHEN A.TOTAL_QTY IS NULL THEN NULL
ELSE A.TOTAL_QTY-NVL(B.TOTAL_QTY,0)
END), 0) AS MONTLY_QTY
FROM T B
WHERE A.MONTH_ID-1=B.MONTH_ID(+)
AND A.NAME=B.NAME(+));

name month_id tamt tqty mamt mqty
----------------------------------
JIMMY 201105 50 10
JOHN 201102 50 15
JOHN 201103 60 25 10 10


So, my question is, is it possible to do it this way? if yes, then where did I do wrong?
How to fix it?

Thank you very much for your instant response.

Tom Kyte
June 20, 2011 - 9:38 am UTC

ops$tkyte%ORA11GR2> select *
  2    from (
  3  select rowid rid,
  4         name, month_id, total_amt, total_qty, monthly_amt, monthly_qty,
  5         total_amt - lag(total_amt) over (partition by name order by month_id) new_monthly_amt,
  6         total_qty - lag(total_qty) over (partition by name order by month_id) new_monthly_qty
  7    from t
  8         )
  9   where new_monthly_amt is not null
 10      or new_monthly_qty is not null
 11   order by name, month_id
 12  /

RID                NAME                             MONTH_ID  TOTAL_AMT  TOTAL_QTY MONTHLY_AMT MONTHLY_QTY NEW_MONTHLY_AMT NEW_MONTHLY_QTY
------------------ ------------------------------ ---------- ---------- ---------- ----------- ----------- --------------- ---------------
AAAWuJAAEAAADwPAAB JOHN                               201103         60         25                                      10              10

ops$tkyte%ORA11GR2> 
ops$tkyte%ORA11GR2> merge into t
  2  using
  3  (
  4  select *
  5    from (
  6  select rowid rid,
  7         total_amt - lag(total_amt) over (partition by name order by month_id) new_monthly_amt,
  8         total_qty - lag(total_qty) over (partition by name order by month_id) new_monthly_qty
  9    from t
 10         )
 11   where new_monthly_amt is not null
 12      or new_monthly_qty is not null
 13  ) x
 14  on ( t.rowid = x.rid )
 15  when matched
 16  then
 17          update set monthly_amt = x.new_monthly_amt, monthly_qty = new_monthly_qty
 18  /

1 row merged.

ops$tkyte%ORA11GR2> select * from t order by name, month_id;

NAME                             MONTH_ID  TOTAL_AMT  TOTAL_QTY MONTHLY_AMT MONTHLY_QTY
------------------------------ ---------- ---------- ---------- ----------- -----------
JIMMY                              201105         50         10
JOHN                               201102         50         15
JOHN                               201103         60         25          10          10

Anand, June 20, 2011 - 3:29 pm UTC

Try this...

merge into t
using (
select * from
(select name
, month_id
, total_amt
, total_qty
, to_char(add_months( to_date(month_id, 'YYYYMM'), -1), 'YYYYMM') prev_month
, lag(month_id) over (partition by name order by month_id) lg_month
, lag(total_amt) over (partition by name order by month_id) lg_ttl_amt
, lag(total_qty) over (partition by name order by month_id) lg_ttl_qty
from
t) s
where s.lg_month is not null ) stg
on (t.name = stg.name
and t.month_id = stg.month_id
)
when matched then
update set t.monthly_amt = t.total_amt - decode(stg.lg_month, stg.prev_month, lg_ttl_amt, 0)
, t.monthly_qty = t.total_qty - decode(stg.lg_month, stg.prev_month, lg_ttl_qty, 0)


Tom Kyte
June 20, 2011 - 3:43 pm UTC

why?

if you are going to join the table back to itself, why would you want to do it by name and month_id when you could.... just do it by rowid?

and you don't need the decodes/etc - it can all be done without.

Anand, June 20, 2011 - 3:54 pm UTC

I agree with you on the rowid.

The reason I used the decode - The person posting the question stated,

"MONTHLY_AMT=(TOTAL_AMT OF CURRENT MONTH) - (TOTAL_AMT OF last MONTH);
if there is no record for last month, just set the value of TOTAL_AMT OF CURRENT MONTH to
MONTHLY_AMT. Same logic with MONTHLY_QTY"
Tom Kyte
June 21, 2011 - 7:35 am UTC

oh, I totally missed that - you are right. I looked at their second set of output with the nulls in it...

Ok, need an NVL


ops$tkyte%ORA11GR2> merge into t
  2  using
  3  (
  4  select *
  5    from (
  6  select rowid rid,
  7         total_amt - nvl(lag(total_amt) over (partition by name order by month_id),0) new_monthly_amt,
  8         total_qty - nvl(lag(total_qty) over (partition by name order by month_id),0) new_monthly_qty
  9    from t
 10         )
 11   where new_monthly_amt is not null
 12      or new_monthly_qty is not null
 13  ) x
 14  on ( t.rowid = x.rid )
 15  when matched
 16  then
 17          update set monthly_amt = x.new_monthly_amt, monthly_qty = new_monthly_qty
 18  /

3 rows merged.

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

NAME                             MONTH_ID  TOTAL_AMT  TOTAL_QTY MONTHLY_AMT MONTHLY_QTY
------------------------------ ---------- ---------- ---------- ----------- -----------
JIMMY                              201105         50         10          50          10
JOHN                               201103         60         25          10          10
JOHN                               201102         50         15          50          15


Anand, June 20, 2011 - 3:57 pm UTC

By the way Tom - The work you do is amazing. Reminds me of the movie, "Multiplicity"... (reference to the existence of the clones to do all the work you do...)

thank you very much :)

phoenixbai, June 20, 2011 - 8:44 pm UTC

With a little adjustment, this is exactly what I need.
Thank you soooo much as always Tom.
Wish you a great day every day!

Thanks a lot Tom.

A reader, July 29, 2011 - 3:30 pm UTC

Thanks a lot Tom.

Osama Shehab, December 19, 2011 - 1:12 am UTC

Hi Tom,

This is great,
Update
( Select Old.Col1,Old.Col2, ..., New.Col1,New.Col2, ..
Where ..required filters)
Set Old.Col1 = New.Col2,
..
..

but it seems that I must have the update privs. on New table as well? What if the source is a read only view? how can I workaround this.

Thank you in advance
Tom Kyte
December 19, 2011 - 7:40 am UTC

you can use merge instead.


merge into old
using new
on (old.keys = new.keys)
when matched then update set col1 = new.col1, col2 = new.col2, ....

Rahul, February 01, 2012 - 9:45 am UTC

Hi Tom,

I've same query as you suggested.

Update
( Select Col1,Col2, NewCol1, NewCol2
From A
Where ..required filters ) z
Set z.Col1 = z.Col1 + z.NewCol1,
z.Col2 = z.Col2 + z.NewCol2 ;

There are ~238,000,000 rows in Table 'A'.
When I run SELECT statement separately, it returns ~50,000 rows in 1-2 minutes,
but UPDATE statement takes around 5 hours to execute.

What should be reason ?

Thanks....



Tom Kyte
February 01, 2012 - 9:53 am UTC

compare the plans, are they different.

and updating 50,000 rows involves updating indexes as well potentially, is there an index update here anywhere?

A reader, February 02, 2012 - 6:57 am UTC

I was wrong in my previous post.
It's actually the SELECT query taking too much time.

As mentioned earlier,
There are ~238,000,000 rows in Table 'A'.
SELECT statement returns ~50,000 rows.
UPDATE statement takes around 5 hours to execute.

Index details are as follow :
PK UNIQUE NORMAL pk_col
INDEX1 NONUNIQUE BITMAP col4, col5, col7, col8
INDEX2 NONUNIQUE BITMAP col4, col5, col15
INDEX3 NONUNIQUE NORMAL col4, col16, col5
INDEX4 UNIQUE NORMAL col4, pk_col
INDEX5 NONUNIQUE NORMAL col4

There are no Indexes on updated columns.
There are NOT NULL constrains on all updated columns.

Following is my original query.
Could you please suggest something to improve performance.


UPDATE
(
SELECT /*+ INDEX_COMBINE (a,INDEX1)*/
a.col1,
a.col2,
a.col3,
--
CASE val1
WHEN 1 THEN
CASE
WHEN user_func1(paramerets) IS NULL THEN 1000
ELSE 0
END
ELSE 0
END
+
CASE val2
WHEN 1 THEN
CASE
WHEN user_func2(paramerets) = 1 THEN 2000
ELSE 0
END
ELSE 0
END
+
CASE val3
WHEN 1 THEN
CASE
WHEN user_func3(paramerets) IS NULL THEN 3000
ELSE 0
END
ELSE 0
END
+
CASE val4
WHEN 1 THEN
CASE
WHEN user_func4(paramerets)=1 THEN 4000
ELSE 0
END
ELSE 0
END
col1_new,
--
CASE user_pkg.func5 (paramerets)
WHEN 0 THEN
CASE val1
WHEN 2 THEN
CASE
WHEN user_func1(paramerets) IS NULL THEN 1000
ELSE 0
END
ELSE 0
END
ELSE 0
END
+
CASE user_pkg.func5 (paramerets)
WHEN 0 THEN
CASE val2
WHEN 2 THEN
CASE
WHEN user_func2(paramerets) =1 THEN 2000
ELSE 0
END
ELSE 0
END
ELSE 0
END
+
CASE user_pkg.func5 (paramerets)
WHEN 0 THEN
CASE val3
WHEN 2 THEN
CASE
WHEN user_func3(paramerets) IS NULL THEN 3000
ELSE 0
END
ELSE 0
END
ELSE 0
END
+
CASE user_pkg.func5 (paramerets)
WHEN 0 THEN
CASE val4
WHEN 2 THEN
CASE
WHEN user_func4(paramerets) = 1 THEN 4000
ELSE 0
END
ELSE 0
END
ELSE 0
END
col2_new,
--
CASE val1
WHEN 3 THEN
CASE
WHEN user_func1(paramerets) IS NULL THEN 1000
ELSE 0
END
ELSE 0
END
+
CASE val2
WHEN 3 THEN
CASE
WHEN user_func2(paramerets) = 1 THEN 2000
ELSE 0
END
ELSE 0
END
+
CASE val3
WHEN 3 THEN
CASE
WHEN user_func3(paramerets) IS NULL THEN 3000
ELSE 0
END
ELSE 0
END
+
CASE val4
WHEN 3 THEN
CASE
WHEN user_func4(paramerets)=1 THEN 4000
ELSE 0
END
ELSE 0
END
col3_new
from A
where a.col4 = 10 -- list Partitions based on this column -- 12 Distinct values
and a.col5 =
and a.col6_date between to_date('04012009', 'mmddyyyy' ) and to_date('06302009', 'mmddyyyy' )
and a.col7 in (1, 2, 3) -- 4 Distinct values
AND a.col8 IN ('N', 'Y') -- 2 Distinct values
AND a.col9 IS NOT NULL
AND a.col6_date IS NOT NULL
and ( 10 = 10 or a.col10 = 10 ) -- 2 Distinct values
)
zz
--
SET zz.col1 = zz.col1 + zz.col1_new,
zz.col2 = zz.col2 + zz.col2_new,
zz.col3 = zz.col3 + zz.col3_new ;

Rahul, February 02, 2012 - 8:41 am UTC

Thanks Tom for answering..

I have gone through the article you mentioned above.
It is very helpful.

It seems you are suggesting to use "scalar subquery" in my scenario. Am I right ?

It seems I need to replace all function calls with ( select Fun() from dual ).

In my case, something like below. Right ?

UPDATE
(
SELECT /*+ INDEX_COMBINE (a,INDEX1)*/
a.col1,
a.col2,
a.col3,
--
CASE val1
WHEN 1 THEN
CASE
WHEN ( select user_func1(paramerets) from dual ) IS NULL THEN 1000
ELSE 0
END
ELSE 0
END
+
CASE val2
WHEN 1 THEN
CASE
WHEN ( select user_func2(paramerets) from dual ) = 1 THEN 2000
ELSE 0
END
ELSE 0
END
+
CASE val3
WHEN 1 THEN
CASE
WHEN ( select user_func3(paramerets) from dual ) IS NULL THEN 3000
ELSE 0
END
ELSE 0
END
+
CASE val4
WHEN 1 THEN
CASE
WHEN ( select user_func4(paramerets) from dual ) = 1 THEN 4000
ELSE 0
END
ELSE 0
END
col1_new
........

Do I need to include RESULT_CACHE in function to use "Scalar Subquery" effectively ?

Thanks....

Tom Kyte
February 02, 2012 - 9:14 am UTC

It seems you are suggesting to use "scalar subquery" in my scenario. Am I right
?


yes.


Do I need to include RESULT_CACHE in function to use "Scalar Subquery"
effectively ?


IF you can result_cache it AND result_cache-ing it makes sense
THEN 
   by all means, result cache it, it won't hurt and may help
END IF

IF the function is deterministic
THEN 
   by all means, mark it so, it won't hurt and may help
END IF

and then add the scalar subquery on top of it all


you don't HAVE TO, but if you can - it won't hurt you and might help

Rahul, February 02, 2012 - 9:37 am UTC

Thank you very much Tom for your help..

Update a column based on the sort order of another column

Dhruva, February 08, 2012 - 11:04 am UTC

I have an existing table where I want to update a sort order column depending on the alphabetical order of some other column.
DROP TABLE test_table;

CREATE TABLE test_table (
  code       VARCHAR2(30),
  sort_order NUMBER);

INSERT INTO test_table
  SELECT object_name, NULL
  FROM   all_objects
  WHERE  ROWNUM < 10;

COMMIT;

UPDATE (
  SELECT   ROWNUM row_num,
           code,
           sort_order
  FROM     test_table
  ORDER BY code)
SET sort_order = row_num * 10;

Obviously, the update results in error: ORA-01732: data manipulation operation not legal on this view (because of the virtual column).

Using an analytical function also results in the same error:
UPDATE (
  SELECT   code,
           RANK () OVER (ORDER BY code) ra_nk,
           sort_order
  FROM     test_table
  ORDER BY code)
SET sort_order = ra_nk * 10;

Is there an elegant way of doing this?

Thanks
Tom Kyte
February 08, 2012 - 12:18 pm UTC

Obviously, the update results in error: ORA-01732: data manipulation operation not legal on this view (because of the virtual column).

not only that - but since rownum is assigned BEFORE sorting, it would result in "garbage" had it worked!

ops$tkyte%ORA11GR2> CREATE TABLE test_table (
  2    code       VARCHAR2(30),
  3    sort_order NUMBER);

Table created.

ops$tkyte%ORA11GR2> 
ops$tkyte%ORA11GR2> INSERT INTO test_table
  2    SELECT object_name, NULL
  3    FROM   all_objects
  4    WHERE  ROWNUM < 10;

9 rows created.

ops$tkyte%ORA11GR2> 
ops$tkyte%ORA11GR2> COMMIT;

Commit complete.

ops$tkyte%ORA11GR2> 
ops$tkyte%ORA11GR2> merge into test_table
  2  using ( select rowid rid, row_number() over (order by code) rn from test_table ) x
  3  on ( test_table.rowid = x.rid )
  4  when matched then update set sort_order = x.rn;

9 rows merged.

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

CODE                           SORT_ORDER
------------------------------ ----------
ICOL$                                   3
I_USER1                                 7
CON$                                    1
UNDO$                                   9
C_COBJ#                                 2
I_OBJ#                                  6
PROXY_ROLE_DATA$                        8
I_IND1                                  5
I_CDEF2                                 4

9 rows selected.


SQL

Sanchi, March 03, 2012 - 1:43 am UTC

hello, sir
I want to know that if i want to require data from 2 tables given below but there is same meter no Please send me select query according to this the tables are..
bills:
bill no,
meter no,
previousreading,
currentReading,
amount,
second table is Customer:
custid,
name,
unitrate,
meterno,
reading,
connectiontype,
Tom Kyte
March 03, 2012 - 8:41 am UTC

huh? no clue what you might mean here.

and I don't see a create table
I don't see any inserts

which means you won't see a select from me.


Make it easy for the person you are asking help from. give them a small sample (but a correct and complete example!). explain in detail what you need.

I want to know that if i want to require data from 2 tables given below but there is same meter no

that doesn't tell us anything - doesn't actually tell us anything.

sumit, July 03, 2012 - 7:43 am UTC

hi tom
I have doute it should count from left

select instr('sumitishappay','p',-1)from dual;
Tom Kyte
July 03, 2012 - 8:52 am UTC

ops$tkyte%ORA11GR2> rem sumitishappay
ops$tkyte%ORA11GR2> rem 1234567890123
ops$tkyte%ORA11GR2> rem           ^ 11th
ops$tkyte%ORA11GR2> select instr('sumitishappay','p',-1)from dual
  2  /

INSTR('SUMITISHAPPAY','P',-1)
-----------------------------
                           11



and indeed it did. It found the last P because it started at the end of the string.

it always returns the position in the string where it found the pattern.

A reader, July 04, 2012 - 7:24 am UTC

Thax..but if i give -1 so it should count from last.
as u told It found the last P because it started at the end of the string.if it started at the of the string so ans should be 3.like substr

select substr('sumitishappay',-7,5)from dual o/p is shapp.

select instr('sumitishappay','p',1)from dual o/p 10
select instr('sumitishappay','p',-1)from dual o/p 11
is it counting from last or not .......
Tom Kyte
July 05, 2012 - 7:17 am UTC

it always gives the position of the character in the string, it does what it does, not what you think it does.

All software is like that, it does what it does, not what you think it should do based on your interpretation of it.


who is "u"?????


If it starts at the end of the string and looks backs for P, it will find P as POSITION 11 in the string.


If it starts at the end of the string and looks for U, it will find it at POSITION 2 in the string.

It is not COUNTING, it is returning a position in the string of a matching string.

from the documentation:

If a substring that is equal to substring is found, then the function returns an integer indicating the position of the first character of this substring.

No counting, rather "returns a position" - 1, 2, 3, ... N


It is SEARCHING from the last
It then returns the POSITION of the string

will PK FK be messed up?

A reader, July 04, 2012 - 9:44 pm UTC

Hi Tom,
I noticed you mentioned '' in many thread.
My question is whether disable-enable will make the PK and FK mess up?
Of course, when enabling it any error or inconsistent will be reported out.
But whether the time saved by disable it overvalue the time we used to fix the issue during re-enable it?
Tom Kyte
July 05, 2012 - 8:26 am UTC

''? what is ''?

But whether the time saved by disable it overvalue the time we used to fix the
issue during re-enable it?


I don't know what you mean.


sumit india

A reader, July 07, 2012 - 5:29 am UTC

thx..i get it for that this is a wonderful ex:-

select instr ('abcabcabc','c',1) from dual; o/p-3
SELECT INSTR ('abcabcabc','c',1,2) FROM DUAL; o/p-6
SELECT INSTR ('abcabcabc','c',1,2) FROM DUAL; o/p-9
SELECT INSTR ('abcabcabc','c',-1,2) FROM DUAL; o/p-6
select instr ('abcabcabc','c',-1,1) from dual; o/p-9

sambit baliarsingh, November 14, 2012 - 1:12 am UTC

hi tom i have a question on the update .following is my scenario

i have created a table calles EMP1 with the same structure as EMP

then i updated the emp1 table setting the sal=sal+1000 and comm=comm+1000

now i again want to update the emp1 sal column with the emp table i wrote the following query

update (select a.sal ,b.sal from emp1 a,emp b where a.empno=b.empno)
set a.sal=b.sal


but while i am trying to execute the query its showing
b.sal is invalid identifier.

please help me because its one of the scenario with which i am working .

Tom Kyte
November 14, 2012 - 8:26 pm UTC

correlation names don't go "up", then can only go "down" a level

select a.sal a_sal, b.sal b_sal from ....


and use a_sal, b_sal in the outer part of the query.

James, November 15, 2012 - 4:34 am UTC

Hi Tom,

I've check all the reviews on this question, but I can't seems to find the right answer on the issue that I've figuring out.

Here is the sample_table1
ID varchar2(5)
BRAND varchar2(2)
PLAN varchar2(10)
DURATION number(4)
REASON varchar2(15)

insert into sample_table1
(101,G,Plan100,20,nonbillable)
(101,G,Plan100,15,nonbillable-unli)
(102,IN,Plan200,30,nonbillable)
(103,IN,Plan200,60,nonbillable)
(104,G,Plan300,10,nonbillable-unli)
(104,G,Plan300,20,nonbillable)
(105,IN,Plan300,5,nonbillable-unli)


lets assume we have those data inside the sample_table1

and here is the

sample_table2
BRAND varchar2(2)
PLAN varchar2(10)
R1_TOT_AVAIL number(5) null
R1_TOT_DURATION number (5) null
R2_TOT_AVAIL number(5) null
R2_TOT_DURATION number (5) null


insert into sample_table2
(G,Plan100,null,null,null,null)
(IN,Plan200,null,null,null,null)
(G,Plan300,null,null,null,null)
(IN,Plan300,null,null,null,null)


lets assume we have those data inside the sample_table2
null values on this table will have to be updated

this was my approach:

UPDATE sample_table2 a
SET R1_TOT_AVAIL = (select count(b.plan) from sample_table1 b where b.plan = a.plan and b.brand = a.brand and trim(b.reason) = 'nonbillable'
                    group by b.plan, b.brand)
    R1_TOT_DURATION = (select sum(b.duration) from sample_table1 b where b.plan = a.plan and b.brand = a.brand and trim(b.reason) = 'nonbillable'
                    group by b.plan, b.brand)
    R2_TOT_AVAIL = (select count(b.plan) from sample_table1 b where b.plan = a.plan and b.brand = a.brand and trim(b.reason) = 'nonbillable-unli'
                    group by b.plan, b.brand)
    R2_TOT_DURATION = (select sum(b.duration) from sample_table1 b where b.plan = a.plan and b.brand = a.brand and trim(b.reason) = 'nonbillable-unli'
                    group by b.plan, b.brand)
WHERE R1_TOT_AVAIL is null
AND R1_TOT_DURATION is null
AND R2_TOT_AVAIL is null
AND R2_TOT_DURATION is null


Question:
1. Would there be a better approach to update those null columns?
2. Can I ask for suggestion on how I can update those columns in the fastest and most efficient way?

Thank you very much Tom! :)

Will be waiting for you response.
Tom Kyte
November 19, 2012 - 9:21 am UTC

funny, I get all kinds of errors when I try to put your schema in place? the create tables don't actually create anything and the inserts don't actually work???

in the future post working creates and inserts if you want working SQL...


I would use merge in order to avoid indexes if the amount of data to be updated was more than a small number of rows...


ops$tkyte%ORA11GR2> merge into
  2  (select *
  3     from t2
  4    where r1_tot_avail is null
  5      and r1_tot_duration is null
  6          and r2_tot_avail is null
  7      and r2_tot_duration is null ) new_t2
  8  using
  9  (select plan, brand,
 10          count( case when reason = 'nonbillable' then plan     end ) new_r1_tot_avail,
 11          sum  ( case when reason = 'nonbillable' then duration end ) new_r1_tot_duration,
 12          count( case when reason = 'nonbillable-unli' then plan     end ) new_r2_tot_avail,
 13          sum  ( case when reason = 'nonbillable-unli' then duration end ) new_r2_tot_duration
 14     from t1
 15    group by plan, brand ) new_t1
 16  on (new_t2.brand = new_t1.brand and new_t2.plan = new_t1.plan)
 17  when matched
 18  then update set r1_tot_avail = new_r1_tot_avail,
 19                  r1_tot_duration = new_r1_tot_duration,
 20                  r2_tot_avail = new_r2_tot_avail,
 21                  r2_tot_duration = new_r2_tot_duration
 22  /

3 rows merged.

updating

A reader, December 11, 2012 - 9:25 am UTC

Performing update based on other table. if there is record then update with its value if no value found then use some column of table being upating. Following query update by values from other table but does not use value of updating column. The values for dept other than 10 are empty after execution of this query.

CREATE TABLE SCOTT.TEST1
( DEPTNO NUMBER(10,0),
DESC1 VARCHAR2(50 BYTE)
);
CREATE UNIQUE INDEX "SCOTT"."UC_DEPT" ON "SCOTT"."TEST1" ("DEPTNO") ;
Insert into TEST1 (DEPTNO,DESC1) values (10,'K1');
alter table emp add(dept_desc varchar2(100), desc1 varchar2(100))
update emp set (dept_desc,desc1)=
(select NVL2(test1.desc1,test1.desc1,SUBSTR(ENAME,1,5)),NVL2(test1.desc1,test1.desc1,SUBSTR(job,1,5)) from test1 where emp.deptno=test1.deptno(+))

but if execute query then it returns proper result and it does not update properly when using for update.
select NVL2(test1.desc1,test1.desc1,SUBSTR(ENAME,1,5)),NVL2(test1.desc1,test1.desc1,SUBSTR(job,1,5)) from test1 ,emp where emp.deptno=test1.deptno(+)
Tom Kyte
December 17, 2012 - 3:03 pm UTC

... if there is record then update with its
value if no value found then use some column of table being upating. ...

sorry, compilation failed, syntax error line 1 - no idea what you mean by that statement.


I think - based on your failed code snippet - that you wanted to

update emp and set it equal to test1.desc1 if there was a matching record in test1, otherwise - default it to ename/job in the emp table.

outer joins don't work the way you think - your update "select" portion is just like:


select ... from t where t.column = 5(+);


you need to outer join to a "set" - an outer join of A to B returns every row in B at least once. An outer join of A to (nothing) might return nothing.


merge into emp 
using (select emp.deptno, 
              NVL2(test1.desc1,test1.desc1,SUBSTR(ENAME,1,5)) d1,
              NVL2(test1.desc1,test1.desc1,SUBSTR(job,1,5)) d2
         from test1 ,emp 
        where emp.deptno=test1.deptno(+)) YOUR_QUERY
on (emp.deptno = your_query.deptno)
when matched then update set dept_desc = d1, desc1 = d2;

A reader, December 18, 2012 - 9:58 am UTC

the merge might fail if there are multiple records for a given deptno in emp, even though the value for desc1 will be same per deptno as there is unique index define on test1.deptno.
Tom Kyte
December 18, 2012 - 1:21 pm UTC

this is why I should just never ever answer anything that doesn't come with create tables, inserts (test data) and all. anytime I don't actually test it - it doesn't work (imagine that!)

merge into emp
using (select emp.rowid rid,
              NVL2(test1.desc1,test1.desc1,SUBSTR(ENAME,1,5)) d1,
              NVL2(test1.desc1,test1.desc1,SUBSTR(job,1,5)) d2
         from test1 ,emp
        where emp.deptno=test1.deptno(+)) YOUR_QUERY
on (emp.rowid = your_query.rid)
when matched then update set dept_desc = d1, desc1 = d2;


thanks!!!!!
of course we have to update by the primary key (or rowid) of EMP.

A reader, December 18, 2012 - 1:41 pm UTC

Sorry Tom I know thats is not sufficient information from OP I am just pointing out that it might fail.

I agree with you when not given enough information only thing left is wild guess :)

table problem

maujood, February 25, 2013 - 3:16 am UTC

i mmaujood ali khan

oracle 10g installed and started working
when i create a table and then inserting the value to this table its not showing the table column in properway.

please tell me the solution

pls help me

shakthi, May 29, 2013 - 12:24 am UTC

i am having 2 tables

1.student_master

regno papercode name m1 m2 m3 total result

2. student_arrear

regno papercode name m1 m2 m3 total result

i need to update student_master from student_arrear those who have passed i have to match the regno and papercode.pls send sql query for this

Tom Kyte
May 29, 2013 - 7:06 pm UTC

no create
no inserts
no look

even more, no explanation of what "who have passed" - what defines "who have passed". I see a regno (no explanation of what that is), papercode (same comment), name (i guess a student name???) m1, m2, m3 ??????, total (what is total???) result (same here????)


no clue what you need to do.

dynamic cursor

Tony Fernandez, June 05, 2013 - 2:30 pm UTC

Dear Tom,

I have to go through about 200 tables and merge into same tables but with new version.
So table1 will be merged into table1_new, but would like to write generic procedure/package for all 200 at once and not 200 procedures.

The challenge is that when doing the update portion of merge, as in update table1_new set row = orig_row, orig_row needs to be defined as %rowtype of the table in the loop for 200 tables. I was able to define a generic cursor with ref cursor, but can't define a generic %rowtype off of that cursor.

Is there a way to do this or perhaps a better approach?

Your reply is appreciated.
Tom Kyte
June 05, 2013 - 2:35 pm UTC

write one bit of code to generate the other bits of code.


don't write 200 procedures.

write one procedure that will write 200 procedures. Use a naming convention so it would be easy to read these out - dynamically execute them - and then drop them later.




dynamic cursor

Tony Fernandez, June 05, 2013 - 2:32 pm UTC

Tom,

In my post above, I am not including any code or pseudo code due to the fact that the organization I am working on filters any outgoing "code" thru email or it will be stopped.

Thanks,
Tom Kyte
June 05, 2013 - 2:36 pm UTC

this isn't "email", you can type code in here, just make it sanitized code

dynamic cursor

Tony Fernandez, June 06, 2013 - 2:50 pm UTC

Nah,

in definition portion we have

c0 ref cursor;
c1 c0;

then somewhere I need to define a variable that holds the rowtype for that unknown yet ref cursor.

That c1 cursor will only know its structure when the cursor is opened with open c1 with sql_stmt.

I hope this makes sense.

Regards,



Tom Kyte
June 06, 2013 - 3:29 pm UTC

you'll be using dbms_sql then, not a ref cursor. ref cursors have to be known at COMPILE time, you'd need the structure to be known when you COMPILE.

with dbms_sql, you'll have one procedure that can process all 200 tables, it can describe what the cursor returns, it can bind to what the cursor returns and it can fetch an arbitrary row with an unknown (at compile time) set of columns.


and you *can* do it the way I said.

You have 200 tables.

loop over them in the dictionary, generate code for them.

then run said code



Don't use a cursor

David P, June 07, 2013 - 12:25 am UTC

Tony Fernandez, if you are merging data into another table, don't use a cursor at all - If at all possible, query user_tab_columns to build a native dynamic MERGE statement to do the data move. It will be vastly faster and more understandable.

pradeep, June 13, 2013 - 2:26 pm UTC

i have two tables names DETAILS and STATUS
DETALIS contains eid,ename,sal and STATUS contains
eid,sal,updationdate.
now if i want to perform update operation on DETAILS table i need get information on MESSAGE table. how can i write the querry please help me
thanks regard pradeep
Tom Kyte
June 18, 2013 - 3:20 pm UTC

no create
no inserts
no look

(seems to be light on details too - like what you mean exactly - you talk about details and status but then all of a sudden message pops up????)

re: pradeep

Greg, June 19, 2013 - 4:09 pm UTC

Pradeep, perhaps this will help?

Every update statement ... ever .. summarized and outlined. Just follow the prompts, and build your own! :)

http://docs.oracle.com/cd/E11882_01/server.112/e26088/statements_10008.htm#SQLRF01708

Update table from another table

mkh, September 24, 2013 - 4:45 pm UTC

Hi Tom,

There are 2 tables

Table1

type cpty amt1 amt2
c a 10 0
c a 20 0
nc a 30 0
nc a 40 0
c b 50 0
c b 60 0
nc b 70 0
nc b 80 0

Table2

amtA cpty

100 a
115 a
120 a
125 b
130 b
140 b


Trying to write an single update statement that will do the following

1. Only nc type amt2 column in table1 should be updated
2. amt2 = sum(amtA) from table2 joined by cpty * amt1 / sum(amt1)(cpty wise)

sample output


type cpty amt1 amt2
c a 10 0(type c no update)
c a 20 0(type c no update)
nc a 30 30*(100+115+120) / (30+40)
nc a 40 40*(100+115+120) / (30+40)
c b 50 0 (type c no update)
c b 60 0(type c no update)
nc b 70 70*(125+130+140)/(70+80)
nc b 80 80*(125+130+140)/(70+80)



Plsql or SQL

mkh, October 07, 2013 - 4:16 pm UTC

I managed to make it in plsql, but am eager to find if it is possible to be done under a single update statement