322

Table 1:

id    name    desc
-----------------------
1     a       abc
2     b       def
3     c       adf

Table 2:

id    name    desc
-----------------------
1     x       123
2     y       345

In oracle SQL, how do I run an sql update query that can update Table 1 with Table 2's name and desc using the same id? So the end result I would get is

Table 1:

id    name    desc
-----------------------
1     x       123
2     y       345
3     c       adf

Question is taken from update one table with data from another, but specifically for oracle SQL.

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
Muhd
  • 24,305
  • 22
  • 61
  • 78
  • 2
    possible duplicate of [sql update query with data from another table](http://stackoverflow.com/questions/5036918/sql-update-query-with-data-from-another-table) – p.campbell Aug 11 '11 at 18:07
  • You need to go back to your other question, un-accept that answer, and state specifically that you need the Oracle PLSQL syntax. – p.campbell Aug 11 '11 at 18:08
  • 3
    @p.campbell, That isn't my question... – Muhd Aug 11 '11 at 18:09
  • 2
    Oh I see. So you copy-pasted the question body, but modified to include the Oracle bit. – p.campbell Aug 11 '11 at 18:15
  • 2
    Yeah. And this probably isn't the best example since "desc" is a reserved word, but oh well. – Muhd Aug 11 '11 at 20:25

8 Answers8

629

This is called a correlated update

UPDATE table1 t1
   SET (name, desc) = (SELECT t2.name, t2.desc
                         FROM table2 t2
                        WHERE t1.id = t2.id)
 WHERE EXISTS (
    SELECT 1
      FROM table2 t2
     WHERE t1.id = t2.id )

Assuming the join results in a key-preserved view, you could also

UPDATE (SELECT t1.id, 
               t1.name name1,
               t1.desc desc1,
               t2.name name2,
               t2.desc desc2
          FROM table1 t1,
               table2 t2
         WHERE t1.id = t2.id)
   SET name1 = name2,
       desc1 = desc2
Justin Cave
  • 227,342
  • 24
  • 367
  • 384
  • 9
    In your first code example: Is the outer WHERE-clause necessary for correct results? Or do you use it only to speed up the query? – Mathias Bader Aug 05 '13 at 07:53
  • 60
    @totoro - In the first example, the `WHERE EXISTS` prevents you from updating a row in `t1` if there is no matching row in `t2`. Without it, every row in `t1` will be updated and the values will be set to `NULL` if there is no matching row in `t2`. That is generally not what you want to happen so the `WHERE EXISTS` is generally needed. – Justin Cave Aug 05 '13 at 15:16
  • 5
    It's worth adding that the `SELECT ... FROM t2` **must** result in a unique row. This means that you have to select on all the fields which comprise a unique key -- a non-unique primary key is not sufficient. Without uniqueness, you are reduced to something like [@PaulKarr's loop](http://stackoverflow.com/a/17333889/1204258) -- and if there is not a unique correlation, then more than one target row may be updated for each source row. – Andrew Leach Dec 17 '13 at 16:12
  • Would the WHERE EXISTS be better as WHERE t1.id IN (select id from t2) so you get a hash join instead of an EXISTS query on every row? – Nathan Spears Apr 03 '14 at 22:20
  • @NathanSpears - It depends. Recent versions of Oracle are pretty good at producing the optimal plan whether you're using `IN` or `EXISTS`. If there is a difference, if you expect that the inline query is generally going to return a relatively large fraction of the rows in the target table, an `EXISTS` would generally be more efficient, an `IN` would generally be more efficient if you expect to return a relatively small fraction of the rows in the target table. Of course "small" and "large" are relative terms so the only real answer is to benchmark and see. – Justin Cave Apr 03 '14 at 22:23
  • 3
    Explanation on key-preserved requirement for updatable joins: https://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:548422757486 – Vadzim Feb 12 '15 at 12:25
  • Thank you for sharing this wonderful answer. Just want to ask, i have a similar case and when using the above method(Correlated update) getting message that "Single-row subquery returns mre than one row". Can you please help? Thanks. – RachitSharma Apr 19 '17 at 13:28
  • 1
    @RachitSharma - That means that your subquery (the query from `table2`) is returning multiple rows for one or more `table1` values and Oracle doesn't know which one you want to use. Normally, that means that you need to refine the subquery so that it returns a single distinct row. – Justin Cave Apr 20 '17 at 18:41
  • 1
    wrong answer performance wise, there is `MERGE` command specifically for that – Toolkit May 31 '18 at 13:51
  • My concern about the "where exists" query is that it requires two accesses to table2. – Brian Fitzgerald Apr 26 '20 at 20:00
  • 1
    Using merge is definitely better than using "update ... where exists ...", except when you want to update the field used to join the two tables. Merge does not work in this case, and you have to use this "update" solution. – C J Jun 05 '20 at 23:19
238

Try this:

MERGE INTO table1 t1
USING
(
-- For more complicated queries you can use WITH clause here
SELECT * FROM table2
)t2
ON(t1.id = t2.id)
WHEN MATCHED THEN UPDATE SET
t1.name = t2.name,
t1.desc = t2.desc;
Adrian
  • 7,745
  • 5
  • 28
  • 28
  • 8
    Very fast indeed, 1159477 rows merged in 15,5s – jefissu Aug 13 '18 at 14:52
  • 5
    I hope everybody visiting this question after 2015 notices this answer. Note that this also works if `table1` and `table2` are the same table, just take care of the `ON`-part and the `WHERE`-clause for the `SELECT`-statement of `table2`! – sjngm Feb 25 '19 at 07:46
  • 7
    I find that every time I need to do another merge I keep coming back to this answer for inspiration. I might print it out and frame it on my wall – arnehehe Apr 11 '19 at 12:17
  • 2
    Works like charm!! Thx! – davidwillianx Apr 29 '19 at 16:42
  • SELECT DISTINCT ID, FIELD1, FIELD1 FROM table2 WHERE ID IS NOT NULL – Joseph Poirier Jan 16 '20 at 22:09
  • 1
    t1.name = CASE WHEN t2.name is NULL THEN t1.name ELSE t2.name END -- Keeps system from Nulling out values when no value exists in t2. – Joseph Poirier Jan 16 '20 at 23:14
  • solution with MERGE INTO is my favourite, because it's easy to use with complex subselect instead of simple table (t2 may be joined tables with conditions, aggegated...). The code remains readable and processing is quick. – Lukas Apr 15 '20 at 11:14
  • Worked for me in old Oracle 10g2. – gridtrak Oct 28 '20 at 15:59
  • No idea if MERGE is a non-standard Oracle invention, but it does the job in a way that is much less abstract than the methods trying to force UPDATE do the job – Roland Dec 21 '20 at 19:44
  • This is very very fast compared with other queries. Could you please improve the answer with the WITH clause? Thanks! – Nicolaesse Nov 09 '21 at 13:14
  • What would cause this error to throw when running the MERGE query - "ORA-30926: unable to get a stable set of rows in the source tables", as my source table/query has multiple key ids. I use distinct select in my inner query or for the table2 query although. ```merge into sa.table1 t1 using (select distinct userid, keylinkflag from table2)src on (t1.userid = src.userid) when matched then update set t1.keylinkflag = src.keylinkflag, t1.manual_linking_flag = 1, t1.link = null WHERE t1.keylinkflag is null; ``` – Ak777 Dec 30 '21 at 05:59
  • I solved this. This was due to the data dupes and i had to do a group by and add more filters to pick only one for each combo. – Ak777 Dec 30 '21 at 07:27
26

try

UPDATE Table1 T1 SET
T1.name = (SELECT T2.name FROM Table2 T2 WHERE T2.id = T1.id),
T1.desc = (SELECT T2.desc FROM Table2 T2 WHERE T2.id = T1.id)
WHERE T1.id IN (SELECT T2.id FROM Table2 T2 WHERE T2.id = T1.id);
Yahia
  • 69,653
  • 9
  • 115
  • 144
14
Update table set column = (select...)

never worked for me since set only expects 1 value - SQL Error: ORA-01427: single-row subquery returns more than one row.

here's the solution:

BEGIN
For i in (select id, name, desc from table1) 
LOOP
Update table2 set name = i.name, desc = i.desc where id = i.id;
END LOOP;
END;

That's how exactly you run it on SQLDeveloper worksheet. They say it's slow but that's the only solution that worked for me on this case.

Pau Karr
  • 255
  • 2
  • 7
  • can somebody please explain why this deserves a -2 on reputation? LOL. – Pau Karr Jun 27 '13 at 06:44
  • 14
    I didn't down rate, but it isn't a good solution. Firstly: if the subselect was returning multiple values, then the for loop will be overwriting the name on table2 multiple times for some/all records (not clean). Secondly: there is no order by clause so this will occur in an unpredictable manner (i.e. last value in unordered data wins). Thirdly: It will be much slower. Assuming the outcome of the for loop was intended, the original subselect could have been rewritten in some controlled way to return only 1 value for each record... simplest contrived way would be (select min(name)...) – Alternator Aug 06 '13 at 04:34
  • 3
    If you get multiple values in your subquery, you might rethink the query and use DISTINCT or GROUP BY with MIN, MAX. Just an idea. – Francis Aug 20 '15 at 16:27
  • Long story short: if you can at all avoid it, never ever EVER use any kind of LOOP in a T-SQL statement. Personally, if it wasn't for the 0.001% of the time where there's no other solution, I don't even think it should even be an available function in T-SQL. T-SQL is designed to be set-based, so it works on entire sets of data as a whole; it should NOT be used to work on data line-by-line. – Ray K. Jan 07 '16 at 16:20
  • This is terribly slow and inefficient. You didn't even filter table1! If table1 has 150 million records in it and table2 only has 10, you'lle cycle 150 million times for no reason at all. And even if you did filter it, it's still slow as hell for any large dataset. Use a MERGE statement instead. – Demonblack Jan 17 '18 at 12:39
9

Here seems to be an even better answer with 'in' clause that allows for multiple keys for the join:

update fp_active set STATE='E', 
   LAST_DATE_MAJ = sysdate where (client,code) in (select (client,code) from fp_detail
  where valid = 1) ...

The full example is here: http://forums.devshed.com/oracle-development-96/how-to-update-from-two-tables-195893.html - from web archive since link was dead.

The beef is in having the columns that you want to use as the key in parentheses in the where clause before 'in' and have the select statement with the same column names in parentheses. where (column1,column2) in ( select (column1,column2) from table where "the set I want" );

Mukyuu
  • 6,436
  • 8
  • 40
  • 59
ant
  • 103
  • 1
  • 5
1

Oracle Database 23c has added direct joins for update and delete:

create table t1 (
  c1 int, c2 int
);
create table t2 (
  c1 int, c2 int
);

insert into t1 values ( 1, 1 ), ( 2, 2 );
insert into t2 values ( 1, 42 );

select * from t1;

        C1         C2
---------- ----------
         1          1
         2          2

update t1
set    t1.c2 = t2.c2
from   t2
where  t1.c1 = t2.c1;

select * from t1;

        C1         C2
---------- ----------
         1         42
         2          2

delete t1
from   t2
where  t1.c1 = t2.c1;

select * from t1;

        C1         C2
---------- ----------
         2          2
Chris Saxon
  • 9,105
  • 1
  • 26
  • 42
-1
BEGIN
For i in (select id, name, desc from table2) 
LOOP
Update table1 set name = i.name, desc = i.desc where id = i.id and (name is null or desc is null);
END LOOP;
END;
Disposer
  • 6,201
  • 4
  • 31
  • 38
-2

If your table t1 and it's backup t2 have many columns, here's a compact way to do it.

In addition, my related problem was that only some of the columns were modified and many rows had no edits to these columns, so I wanted to leave those alone - basically restore a subset of columns from a backup of the entire table. If you want to just restore all rows, skip the where clause.

Of course the simpler way would be to delete and insert as select, but in my case I needed a solution with just updates.

The trick is that when you do select * from a pair of tables with duplicate column names, the 2nd one will get named _1. So here's what I came up with:

  update (
    select * from t1 join t2 on t2.id = t1.id
    where id in (
      select id from (
        select id, col1, col2, ... from t2
        minus select id, col1, col2, ... from t1
      )
    )
  ) set col1=col1_1, col2=col2_1, ...
Jim P
  • 569
  • 3
  • 15