Oracle Merge/Insert - sql

I'm trying to see if there is an existing 'IMPORTID' in a table consisting of two columns: 'IMPORTID','STEP'. If there is, increment the record's STEP column by 1.
I've just tested using hardcoded values and there are no rows in the table currently. I'm expecting the following SQL to insert an entry but nothing is happening.
MERGE into TOSTEP t
USING
(SELECT 'Test' IMPORTID, 1 STEP FROM TOSTEP) s
ON
(s.IMPORTID = t.IMPORTID)
WHEN MATCHED THEN
update set t.STEP = s.STEP +1
WHEN NOT MATCHED THEN
insert (IMPORTID,STEP)
values ('Test',1);

You are merging an empty table with itself..meaning there is no data selected in using clause. Change (SELECT 'Test' IMPORTID, 1 STEP FROM TOSTEP) s into (SELECT 'Test' IMPORTID, 1 STEP FROM DUAL) s and it should work
FULL CODE:
MERGE into TOSTEP t
USING
(SELECT 'Test' IMPORTID, 1 STEP FROM DUAL) s
ON
(s.IMPORTID = t.IMPORTID)
WHEN MATCHED THEN
update set t.STEP = s.STEP +1
WHEN NOT MATCHED THEN
insert (IMPORTID,STEP)
values (s.IMPORTID, s.STEP);

Related

Update from another table when row does not exist

I am trying to run below query:
UPDATE tempd tvl
SET id = case
when tse.id is null or tse.id = 0 then --coalesce(nullif(tse.id,0),5)
5
else
tse.event_id end
FROM details tse
WHERE
tvl.id = tse.id
AND tvl.name = tse.name
AND tvl.add = tse.add;
Sample for table tempd:
name add id
a xx
b yy
c zz
d ss
details table is blank now. So every row in tempd should have 5, But I am getting always 0 in id column.
Also, can we update from multiple tables? Like: if there is a match from table1 then take that, if match from table2 take that, else use default value.
Rows excluded by predicates in the WHERE clause are not updated at all. That's the core problem of your query.
You want to update all rows of table tempd unconditionally. A correlated subquery in the assigned value does the job - with no outer WHERE clause:
UPDATE tempd tvl
SET id = COALESCE((
SELECT tse.event_id
FROM details tse
WHERE tse.id = tvl.id
AND tse.name = tvl.name
AND tse.add = tvl.add
AND tse.id <> 0 -- excludes NULL & 0
-- ORDER BY ???
-- LIMIT 1 -- can there be > 1 match?
), 5);
Can we update from multiple tables? Like: if there is a match from table1 then take that, if match from table2 take that, else use default value.
One way:
UPDATE tempd tvl
SET id = COALESCE(NULLIF(
(
SELECT tse.event_id
FROM details tse
WHERE tse.id = tvl.id
AND tse.name = tvl.name
AND tse.add = tvl.add
-- AND tse.id <> 0
-- this time we need to accept 0 to exit
-- ORDER BY ???
-- LIMIT 1 -- can there be > 1 match?
)
, (
SELECT t2.some_id
FROM tbl2 t2
WHERE ... -- your filters here
), 0), 5);
All parentheses are required.
If there can be more than 1 match from each correlated subquery, LIMIT 1 will fix that. But, typically, you'll want to add ORDER BY before each LIMIT 1 to get a deterministic pick.
See:
Nullify column in update if subquery returns empty
PostgreSQL update join vs SQL Server update join
It doesn't matter what the where clause is or the update you're asking for is. If you update from an empty table you will always get zero rows updated.
An even simpler example:
sophia=> create table empty_table(x integer);
CREATE TABLE
sophia=> create table foo(x integer);
CREATE TABLE
sophia=> insert into foo(x) values (1),(2),(3);
INSERT 0 3
sophia=> update foo set x = 5 from empty_table where true;
UPDATE 0
sophia=>
As to the solution - you can pick from one of the following depending on your exact use-case:
Get the value to update from a subquery (as per Erwin's example)
Do something so that details always has data
Update the from so the source always has something (e.g. join with something else)

How to effectively Merge in sql

I have a source table.
id description test
12 this is the some value 0
10 this is the old value 5
And a temp table.(consists of latest changes which needed to be updated in source table)
id description test
10 this is the new value 15
14 this is the some new value 5
Desired output is-> select * from source_table; should give me:
id description test
10 this is the new value 15
14 this is the some new value 5
12 this is the some value 0
Trying to use something like this-
merge into source_table using temp_table
on source_table.id = temp_table.id
when matched then
delete;
insert into source_table (select * from temp_table);
Any suggestions how to achieve this in 1 single query(I am trying to avoid update) ?
If you want a query, you can run:
select id, description, test
from temp
union all
select id, description, test
from source
where not exists (select 1 from temp t where source.id = temp.id);
If you want to modify the data:
merge into source_table
using temp_table
on source_table.id = temp_table.id
when matched then
update set source_table.test = temp_table.test
when not matched then
insert into source_table (id, description, test)
values (temp_table.id, temp_table.description, temp_table.test);
Your naming convention is a little confusing relative to the normal documentation for merge. Typical naming would call "target" what you are calling "source", and then "source" is the new data in temp_table.

Insert first and then update the table using SQL statement

I am trying to use insert and update sql statements.
My table is as follows:
|c1|c2|c3|c4|c5
|1 2 a b c
|1 3 e f g
c3,c4,c5 can have different values. The row can be unique with the combination of C1 and C2 column. I need to be able to check if first row doesn't exists with values c1,c2 then insert the data. If c1,c2 already have the values for eg (1,2) and if the data comes back with the same values for c1,c2 then update c3,c4,c5 with the latest values.
I tried using the following query
INSERT INTO t1 (c1,c2,c3,c4,c5)
VALUES ('1','2','a','b','c')
ON DUPLICATE KEY
UPDATE c3='e',c4 = 'f',c5='g';
I am getting a ORA error as follows
SQL Command not ended properly (ORA-00933)
Update after response from sagi
MERGE INTO table1 t
USING(select '000004' as SENDER,'Receiver' as RECEIVER ,'1030' as IDENTIFIER,'2016' as CREATIONDATEANDTIME,'2' as ACKCODE,'Test' as ACKDESCRIPTION from table1 ) s
ON(t.SENDER = s.SENDER and t.IDENTIFIER = s.IDENTIFIER)
WHEN MATCHED THEN UPDATE SET t.CREATIONDATEANDTIME = '1213',t.RECEIVER = 'hello'
WHEN NOT MATCHED THEN INSERT (t.SENDER,t.RECEIVER,t.IDENTIFIER,t.CREATIONDATEANDTIME,t.ACKCODE,t.ACKDESCRIPTION)
VALUES (s.SENDER,s.RECEIVER,s.IDENTIFIER,s.CREATIONDATEANDTIME,s.ACKCODE,s.ACKDESCRIPTION)
Output of query:
scenario 1: When there is no data matching the condition(t.SENDER = s.SENDER and t.IDENTIFIER = s.IDENTIFIER), I get an error as follows
ORA-30926: Unable to get stable set of rows in the source tables. Cause: A stable set of rows could not be got because of large dml activity or a non-deterministic activity where clause.
Action: Remove any non-deterministic where clause and reissue dml
Scenario 2: When there is data matching the condition (t.SENDER = s.SENDER and t.IDENTIFIER = s.IDENTIFIER) then in the table, I can see 5 new entries.
Can you please help.
You can use MERGE STATEMENT like this:
MERGE INTO t1 t
USING(select '1' as c1,'2' c2 ,'a' as c3,'b' as c4,'c' as c5 from dual) s
ON(t.c1 = s.c1 and t.c2 = s.c2)
WHEN MATCHED THEN UPDATE SET t.c3 = '1213',t.c4 = 'test'
WHEN NOT MATCHED THEN INSERT (t.c1,t.c2,t.c3,t.c4,t.c5)
VALUES (S.c1,s.c2,s.c3,s.c4,s.c5)
This basically perform an UPSERT, update else insert. It checks if the values exist, if so - update/deletes them(adjust to code to do what you want) and if not, insert them.
MERGE INTO table1 t USING(select distinct '000004' as SENDER,'Receiver' as RECEIVER ,'1030' as IDENTIFIER,'2016' as CREATIONDATEANDTIME,'2' as ACKCODE,'Test' as ACKDESCRIPTION from table1 ) s ON(t.SENDER = s.SENDER and t.IDENTIFIER = s.IDENTIFIER) WHEN MATCHED THEN UPDATE SET t.CREATIONDATEANDTIME = '1213',t.RECEIVER = 'hello' WHEN NOT MATCHED THEN INSERT (t.SENDER,t.RECEIVER,t.IDENTIFIER,t.CREATIONDATEANDTIME,t.ACKCODE,t.ACKDESCRIPTION) VALUES (s.SENDER,s.RECEIVER,s.IDENTIFIER,s.CREATIONDATEANDTIME,s.ACKCODE,s.ACKDESCRIPTION)
Added distinct clause in my query and it is working fine. Thanks all for replying to my post and guiding me.

self merge in oracle 11g

Usually there are cases when we need to perform an update or insert into the same table using two different queries. I wanted to see if this could be done on a table using a merge statement.
All I want to know if this can be done or not. Otherwise I'll have to stick with separating the query back into an update/insert operation separately.
Here is what I have so far:
METHOD 1:
MERGE INTO TABLEA TARGET
USING (
SELECT 1 FROM DUAL
) SOURCE
ON (TARGET.TARGET.COLA = '001'
AND TARGET.TARGET.COLB = '1111111'
AND TARGET.COLC = '201302'
)
WHEN MATCHED THEN
UPDATE SET TARGET.COLA = '001'
,TARGET.COLB = '1111111'
,TARGET.COLC = '201304'
,TARGET.CREATEDATE = SYSDATE
,TARGET.USERID = 'USERA'
WHEN NOT MATCHED THEN
INSERT (TARGET.COLA
,TARGET.COLB
,TARGET.COLC
,TARGET.COLD
,TARGET.CREATEDATE
,TARGET.USERID)
VALUES('001'
,'1111111'
,'201304'
,'123'
,SYSDATE
,'USERA')
At first this method made sense to me, because I would always be returning results from the source, and I'd update and insert accordingly. However, oracle refuses to follow this:
SQL Error: ORA-38104: Columns referenced in the ON Clause cannot be updated: "TARGET"."EFF_FISCAL_YR_PD_NBR"
38104. 00000 - "Columns referenced in the ON Clause cannot be updated: %s"
*Cause: LHS of UPDATE SET contains the columns referenced in the ON Clause
METHOD 2:
MERGE INTO TABLEA TARGET
USING (
SELECT ROWID AS RID,COLA,COLB,COLC
FROM TABLEA
WHERE COLA = '001'
AND COLB = '1111111'
AND COLC = '201301'
) SOURCE
ON (TARGET.ROWID = SOURCE.RID)
WHEN MATCHED THEN
UPDATE SET TARGET.COLA = '001'
,TARGET.COLB = '1111111'
,TARGET.COLC = '201304'
,TARGET.CREATEDATE = SYSDATE
,TARGET.USERID = 'USERA'
WHEN NOT MATCHED THEN
INSERT (TARGET.COLA
,TARGET.COLB
,TARGET.COLC
,TARGET.COLD
,TARGET.CREATEDATE
,TARGET.USERID)
VALUES('001'
,'1111111'
,'201304'
,'123'
,SYSDATE
,'USERA')
The logic behind this is, if I try to look up values from the source table, and it matches, it'll find the records and update itself with those values. However, the issue comes when trying to insert if it doesn't match. Because the source is filtered, no records get returned, therefore there's nothing for the target to match on, and nothing gets inserted. What I would like this to do is insert if no record is found in the SOURCE (implicitly not matched against the target), especially since the insert statement contains nothing by values passed in from variables rather than the source itself.
I've tried updating the source to look like this:
SELECT ROWID AS RID,COLA,COLB,COLC
FROM TABLEA
WHERE COLA = '001'
AND COLB = '1111111'
AND COLC = '201301'
UNION ALL
SELECT ROWID,NULL,NULL,NULL FROM DUAL
But the problem with this is that the merge does the update on the record it matched AND an insert on the record it didn't match.
For those of you who want to know why I'm using a ROWID. This is because the design (not by me) indicated that COLA and COLB would be combined primary key that would be used as an index on the table. Duplicates of COLA, COLB, and COLC are not allowed but they are all updateable via the front end interface. I understand the pitfalls of ROWID, but because I'm only working with one table as target and source, regardless of any CRUD operations I perform on the table, the ROWID will always be matched onto itself.
Summary: I got the self merge to work only when performing an update on a matching item, but inserting doesn't work.
Wow this took me a long while to do!
I was on the right track going with method 3 (UNION ALL with a null recordset from dual).
You just need to satisfy three conditions:
You always need to return a resulting set from the source table, but in a way where it wouldn't match with the target.
You can't return both a matching set and a non matching set otherwise you'll do both, an insert and an update
Your primary key is updateable because it matches on multiple columns. I have unique constraints on them so it'll throw an error if I try to make duplicates
So, here is what the source should look like:
SELECT RID,COLA,COLB,COLC FROM
(
SELECT ROWID AS RID,COLA,COLB,COLC
FROM TABLEA
WHERE COLA = '001'
AND COLB = '1111111'
AND COLC = '201301'
UNION ALL
SELECT ROWID,NULL,NULL,NULL FROM DUAL
ORDER BY COLA ASC
) f
WHERE ROWNUM <= 1
So you return one record. If the where clause is satisfied, you order the dataset in ASCENDING ORDER, and return only the top recordset. That way the merge will update based on that. If the where clause (not the one containing the ROWNUM) returns zero values, it'll still return the null recordset and the merge will insert based on that.
More than one record
If you really want to get nutty and get more than one record (in my case, I needed 1), then you have to get a count of the matching record set, using an aggregate (or an analytical function) and stuff it into a variable so that the where clause criteria looks like this:
WHERE ROWNUM <= COUNTOFRETURNEDRESULTS
If I understand you correctly COLA, COLB, and COLC are the composite primary key of TABLEA.
If that's the case, you don't actually need to use ROWID here, and can do what you need by just selecting from dual, then using your composite key in your ON statement like you did in the first attempt.
You don't need to update the primary key columns, so it's okay use them in the ON clause.
MERGE INTO TABLEA TARGET
USING (
SELECT '001' COLA,
'1111111' COLB,
'201301' COLC
FROM DUAL
) SOURCE
ON (TARGET.COLA = SOURCE.COLA
AND TARGET.COLB = SOURCE.COLB
AND TARGET.COLC = SOURCE.COLC
)
WHEN MATCHED THEN
UPDATE SET TARGET.CREATEDATE = SYSDATE
,TARGET.USERID = 'USERA'
WHEN NOT MATCHED THEN
INSERT (TARGET.COLA
,TARGET.COLB
,TARGET.COLC
,TARGET.COLD
,TARGET.CREATEDATE
,TARGET.USERID)
VALUES('001'
,'1111111'
,'201304'
,'123'
,SYSDATE
,'USERA')
merge into MY_TARGET t
using (select 1 from DUAL) s
on (t.COL1 = :p1 )
when matched then
update set t.COL3 = :p3
when not matched then
insert (COL1, COL2, COL3)
values (:p1, :p2, :p3)
you must have something to return in order to make insert

SQL merge command on one table

I am trying to get my head over MERGE sql statement. What I want to achieve is:
Insert new values into the CSScolorOrders table but update corQuantity column if the record with colID and ordID already exist
This is what I ended up with:
MERGE INTO CSScolorOrders AS TARGET
USING (SELECT * FROM CSScolorOrders WHERE ordID = 3) AS SOURCE
ON (SOURCE.colID = 1) WHEN
MATCHED THEN UPDATE SET corQuantity = 1
WHEN
NOT MATCHED BY TARGET
THEN INSERT (colID, ordID, corQuantity) VALUES (1, 3, 1);
Unfortunately it does not raise any exception so I do not know why it doesn't work.
As discussed here, you'll see that a merge is exactly as it sounds. taking two tables and searching for the value you joined them on lets call it "X". if X is a match then you perform an update on that record. If it does not exist then you would perform an insert on the targeted table using the values selected.
In your case i'm not entirely sure if your join
( ON (SOURCE.colID = 1) )
is correct. i'm pretty sure this needs to be
on(Source.colID = Target.colID)
So the full statement should be this:
MERGE INTO CSScolorOrders AS TARGET
USING (SELECT * FROM CSScolorOrders WHERE ordID = 3) AS SOURCE
on(Source.colID = Target.colID)
WHEN MATCHED THEN
UPDATE SET corQuantity = 1
WHEN NOT MATCHED BY TARGET
THEN INSERT (colID, ordID, corQuantity) VALUES (1, 3, 1);
but i haven't tested this and am not 100% sure what your table columns are and what exactly you're attempting to join. But the link i provided should point you in the right direction.
Hope this helps!
MERGE INTO accounting_values AS Target
USING (select #entity_id as entity_id, #fiscal_year as fiscal_year, #fiscal_period as fiscal_period) AS source
ON (target.entity_id = source.entity_id AND target.fiscal_year = source.fiscal_year AND target.fiscal_period = source.fiscal_period)
WHEN MATCHED THEN
UPDATE SET value = #value
WHEN NOT MATCHED BY Target THEN
INSERT
(entity_id, fiscal_year, fiscal_period, value)
VALUES (#entity_ID, #fiscal_year, #fiscal_period, #value);
This is tested. It doesn't reference the table twice. I was trying to duplicate MySQL's ON DUPLICATE KEY UPDATE.
MERGE CSScolorOrders AS TARGET
USING (SELECT * FROM CSScolorOrders WHERE ordID = 3) AS SOURCE
ON (SOURCE.colID = TARGET.colID) WHEN
MATCHED THEN UPDATE SET corQuantity = 1
WHEN
NOT MATCHED
THEN INSERT (colID, ordID, corQuantity) VALUES (1, 3, 1);
To UPDATE or INSERT in a table whether this table contains a record or not, you can you MERGE as follows:
ensure USING clause returns ONE entry
express the matching condition(s) in the ON clause
In your case (example below tested on Oracle):
MERGE INTO CSScolorOrders AS TARGET
USING (SELECT 'OneEntry' FROM DUAL) AS SOURCE
ON (colID = 1 and ordID = 3) WHEN
MATCHED THEN UPDATE SET corQuantity = 1
WHEN
NOT MATCHED BY TARGET
THEN INSERT (colID, ordID, corQuantity) VALUES (1, 3, 1);
Most databases have a DUMMY table allowing arbitrary SELECT (above, I use DUAL), but if we only use the table mentioned in your question, you can e.g. replace
SELECT 'OneEntry' FROM DUAL
with
SELECT COUNT(*) FROM CSScolorOrders