Bug in SQL Server MERGE statement - sql

I am trying to write a MERGE statement. If the record exists, update it. If it doesn't exist, insert it.
This query runs, but is says '0 rows affected'. I know that an add_id of '3' does not exist, so I would expect it to insert a row. What am I doing wrong? Sorry, I've never written a merge query before.
MERGE INTO dbo.delay AS TARGET
USING (SELECT * FROM dbo.delay WHERE add_id = 3) AS SOURCE ON (Source.add_id = Target.add_id)
WHEN MATCHED
THEN UPDATE SET my_value = 22
WHEN NOT MATCHED BY TARGET
THEN INSERT (add_id, my_value) VALUES (1, 22);

Related

The following query use for check duplicate data in table then update or insert row

I have the following query use for check duplicate data in table. If match data then update row else insert new row. In my case I have already one matched row in att_log table where emp_id=19.1.0121 and where mp_pk_id='32' AND att_date='2021-10-01', so result should be SET holiday=H in the matched row. But the DECLARE statement run without error and in console show affected row:1, but no change occur in data base, holiday not set to "H".
DECLARE c_emp_id att_log.emp_id%type;
BEGIN
SELECT emp_id
INTO c_emp_id
FROM att_log
WHERE emp_id='19.1.0121'
AND emp_pk_id='32'
AND att_date='2021-10-01' ;
EXCEPTION
WHEN TOO_MANY_ROWS THEN
UPDATE att_log
SET holiday = 'H',
updated_at = '2021-08-22'
WHERE emp_id='19.1.0121'
AND att_date='2021-10-01';
WHEN NO_DATA_FOUND THEN
INSERT INTO att_log (emp_id, emp_pk_id, att_date, holiday,login_time, logout_time)
VALUES ('19.1.0121', '32', '2021-10-01','H','','');
COMMIT WORK;
END;
If I run the query separately without DECLARE statement then data row change happen, but with the above DECLARE statement no change happen in data row in the ORACLE table. What is my fault! Sorry, I am new to ORACLE, and also sorry for poor English.
A MERGE operation can INSERT or UPDATE (and also DELETE) depending on whether the row exists or not.
Here's a working test case:
Test case / fiddle
Example of MERGE:
CREATE TABLE logs (
id NUMBER GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL
, text VARCHAR2(20) UNIQUE
, q int DEFAULT 1
);
INSERT INTO logs (text) VALUES ('A');
INSERT INTO logs (text) VALUES ('B');
INSERT INTO logs (text) VALUES ('C');
MERGE INTO logs USING (SELECT 'B' AS text FROM dual) cte ON (cte.text = logs.text)
WHEN MATCHED THEN UPDATE SET logs.q = logs.q + 1
WHEN NOT MATCHED THEN INSERT (logs.text)
VALUES (cte.text)
;
Result:
and now we can do this for several existing rows and new rows at once:
MERGE INTO logs USING (
SELECT text FROM logs WHERE text > 'A' UNION
SELECT 'Z' FROM dual
) cte ON (cte.text = logs.text)
WHEN MATCHED THEN UPDATE SET logs.q = logs.q + 1
WHEN NOT MATCHED THEN INSERT (logs.text)
VALUES (cte.text)
;
New Result:
This example will either INSERT a new row when the rows in cte do not exist in logs, and UPDATE any existing rows in logs when matches are found, by incrementing q.

How to use MERGE or Upsert Sql statement

How can i use MERGE Sql Statement or UPDATE statement for my below code. I am having a columnName called MachineName, other column values change but MachineName doesnot change. If the Column MachineName changes i need to insert the new values in a secondrow. If not i need to Update the same row. How can i do this. Is it a right approach ? Please help
MERGE INTO [devLaserViso].[dbo].[Machine] WITH (HOLDLOCK)
USING [devLaserViso].[dbo].[Machine]
ON (MachineName = MachineName)
WHEN MATCHED
THEN UPDATE SET MachineName = L1,ProgramName= ancdh.pgm, TotalCount= 10, RightCount=4,
LeftCount= 3,ErrorCode=0,FinishingTime=fsefsefef
WHEN NOT MATCHED
THEN INSERT (MachineName, ProgramName, TotalCount, RightCount, LeftCount, ErrorCode, FinishingTime)
VALUES (L02, djiwdn.pgm, 11, 5, 4, 0, dnwdnwoin);
I've had success in the past "Selecting" the values to upsert into the USING section of the MERGE command:
MERGE INTO [devLaserViso].[dbo].[Machine] WITH (HOLDLOCK) AS Target
USING (SELECT 'L1' AS MachineName, 'ancdh.pgm' AS ProgramName, 10 AS TotalCount,
4 AS RightCount, 3 AS LeftCount, 0 AS ErrorCode, 'fsefsefef' AS FinishingTime) AS Source
ON (Target.MachineName = Source.MachineName)
WHEN MATCHED
THEN UPDATE SET ProgramName= Source.ProgramName, TotalCount= Source.TotalCount,
RightCount= Source.RightCount, LeftCount= Source.LeftCount,
ErrorCode= Source.ErrorCode, FinishingTime= Source.FinishingTime
WHEN NOT MATCHED
THEN INSERT (MachineName, ProgramName, TotalCount, RightCount, LeftCount, ErrorCode, FinishingTime)
VALUES (Source.MachineName, Source.ProgramName, Source.TotalCount, Source.RightCount,
Source.LeftCount, Source.ErrorCode, Source.FinishingTime);
You can load the new Machine data in a Temporary table and then can use the Merge statement as follows to update the records for which there is already a record in Machine table and will insert a new record if it does not exists in Machine table.
MERGE [devLaserViso].[dbo].[Machine] t WITH (HOLDLOCK)
USING [devLaserViso].[dbo].[TempMachine] s
ON (s.MachineName = t.MachineName)
WHEN MATCHED THEN
UPDATE SET t.MachineName = s.MachineName,t.ProgramName =s.ProgramName
WHEN NOT MATCHED BY TARGET THEN INSERT (MachineName,ProgramName) VALUES (s.MachineName, s.ProgramName);

Having MERGE INTO do 2 things when Not Matched?

I am using the MERGE INTO statement to work with some tables, and I've a simple question. How can I make a Matched/Not Matched Statement do 2 things? For example, I have this and it works:
MERGE INTO VMFG.dbo._AR_Mill1ActiveSchedule t --target
USING #temp --source
On t.Base_ID = #temp.WORKORDER_BASE_ID AND t.Lot_ID = #temp.WORKORDER_Lot_ID
WHEN MATCHED AND #temp.rollstatus = 'R' THEN
UPDATE
SET t.sawStatus = #temp.sawstatus, t.rollStatus = #temp.rollstatus
WHEN NOT MATCHED BY TARGET THEN
INSERT (BASE_ID, LOT_ID, sawStatus, rollstatus, preheatcheck) VALUES (#temp.WorkOrder_Base_ID, #temp.WorkOrder_Lot_ID, #temp.sawStatus, #temp.rollStatus, 'False')
WHEN NOT MATCHED BY SOURCE AND t.SawStatus = 'C' THEN
Delete ;
Drop Table #temp
Now, I want to accomplish something like this:
MERGE INTO VMFG.dbo._AR_Mill1ActiveSchedule t --target
USING #temp --source
On t.Base_ID = #temp.WORKORDER_BASE_ID AND t.Lot_ID = #temp.WORKORDER_Lot_ID
WHEN MATCHED AND #temp.rollstatus = 'R' THEN
UPDATE
SET t.sawStatus = #temp.sawstatus, t.rollStatus = #temp.rollstatus
WHEN NOT MATCHED BY TARGET THEN
INSERT (BASE_ID, LOT_ID, sawStatus, rollstatus, preheatcheck) VALUES (#temp.WorkOrder_Base_ID, #temp.WorkOrder_Lot_ID, #temp.sawStatus, #temp.rollStatus, 'False')
WHEN NOT MATCHED BY SOURCE AND t.SawStatus = 'C' THEN
INSERT INTO _AR_Mill1RemovedWO (BASE_ID, LOT_ID) VALUES (#temp.WorkOrder_Base_ID, #temp.WorkOrder_Lot_ID)
Delete ;
Drop Table #temp
However, when I run this I get the error "Incorrect syntax near the keyword 'INTO'".
I've tried using the OUTPUT clause but I couldn't get it to work correctly, and would rather use an insert statement anyways if possible.
Thank you :)
EDIT:
The Output clause would be awesome to use, but I'll explain why it hasn't worked. When I used it, i.e.:
....
WHEN NOT MATCHED BY SOURCE AND t.SawStatus = 'C' THEN
--INSERT INTO _AR_Mill1RemovedWO (BASE_ID, LOT_ID) VALUES (#temp.WorkOrder_Base_ID, #temp.WorkOrder_Lot_ID)
Delete
OUTPUT deleted.Base_ID, deleted.Lot_ID INTO VMFG.dbo._AR_Mill1RemovedWO
;
Drop Table #temp
I run in to a problem. It adds rows to my table that weren't actually deleted. It's like it's just adding everything, regardless of being deleted. Is there a reason for this?
This is impossible to do with a single MERGE statement. Use the OUTPUT clause to direct information about the writes executed into a table variable. Execute a second statement such as INSERT to write to the second table.

UPSERT in SQLite

I don't want to use REPLACE INTO because it's basically a DELETE and INSERT and it's complicated to use the data from the old columns.
INSERT OR IGNORE is a bit of a hack because all errors are ignored, so this is not an option.
I've read a blog article which uses the following:
UPDATE Table1 SET (...) WHERE Column1='SomeValue'
IF ##ROWCOUNT=0
INSERT INTO Table1 VALUES (...)
I like this approach really much, but I don't know how I can implement this IF-clause with the ##ROWCOUNT in SQLite, this is what I got:
SELECT CASE (SELECT
CASE
WHEN EXISTS(SELECT 1 FROM t WHERE id=2)
THEN 1
ELSE 0
END)
WHEN 1
UPDATE t set a='pdf' WHERE id=2;
ELSE
INSERT INTO t (a) VALUES ('pdf');
END
the SELECT CASE seems to be the only way to use a CASE-clause in SQLite because everything else throws an syntax error. But it's also not possible to use a UPDATE- or INSERT-statement in the SELECT CASE, so this throws an error.
I've tried the following
UPDATE t set a='pdf' WHERE id=2;
CASE WHEN (changes()=0) THEN
INSERT INTO t (a) VALUES ('pdf');
END
but this doesn't work, because the CASE-clause throws an syntax error.
can someone provide an example using ##ROWCOUNT for an UPSERT in SQLite?
SQLite has no built-in UPSERT-like statement that doesn't delete the old record.
You should check the number of changes in your program, and execute the INSERT conditionally.
However, if you really want to do this in SQL, it's possible; but you have to use the INSERT ... SELECT ... form so that you are able to insert zero records, if needed:
BEGIN;
UPDATE t SET a = 'pdf' WHERE id = 2;
INSERT INTO t(id, a) SELECT 2, 'pdf' WHERE changes() = 0;
COMMIT;
You should use sqlite API in this case and write "IF logic" in your application.
sqlite3_prepare16_v2(stmt1, "UPDATE t SET a=? WHERE id=?");
sqlite3_prepare16_v2(stmt2, "INSERT INTO t(id, a) VALUES(?, ?)");
for(...) // iterate by rows to be updated/inserted
{
//set parameter values for stmt1
sqlite3_step(stmt1);
if( !sqlite3_changes(dbh) )
{
//set parameter values for stmt2
sqlite3_step(stmt2);
}
}

Insert into a temporary table and update another table in one SQL query (Oracle)

Here's what I'm trying to do:
1) Insert into a temp table some values from an original table
INSERT INTO temp_table SELECT id FROM original WHERE status='t'
2) Update the original table
UPDATE original SET valid='t' WHERE status='t'
3) Select based on a join between the two tables
SELECT * FROM original WHERE temp_table.id = original.id
Is there a way to combine steps 1 and 2?
You can combine the steps by doing the update in PL/SQL and using the RETURNING clause to get the updated ids into a PL/SQL table.
EDIT:
If you still need to do the final query, you can still use this method to insert into the temp_table; although depending on what that last query is for, there may be other ways of achieving what you want. To illustrate:
DECLARE
id_table_t IS TABLE OF original.id%TYPE INDEX BY PLS_INTEGER;
id_table id_table_t;
BEGIN
UPDATE original SET valid='t' WHERE status='t'
RETURNING id INTO id_table;
FORALL i IN 1..id_table.COUNT
INSERT INTO temp_table
VALUES (id_table(i));
END;
/
SELECT * FROM original WHERE temp_table.id = original.id;
No, DML statements can not be mixed.
There's a MERGE statement, but it's only for operations on a single table.
Maybe create a TRIGGER wich fires after inserting into a temp_table and updates the original
Create a cursor holding the values from insert and then loop through the cursor updating the table. No need to create temp table in the first place.
You can combine steps 1 and 2 using a MERGE statement and DML error logging. Select twice as many rows, update half of them, and force the other half to fail and then be inserted into an error log that you can use as your temporary table.
The solution below assumes that you have a primary key constraint on ID, but there are other ways you could force a failure.
Although I think this is pretty cool, I would recommend you not use it. It looks very weird, has some strange issues (the inserts into TEMP_TABLE are auto-committed), and is probably very slow.
--Create ORIGINAL table for testing.
--Primary key will be intentionally violated later.
create table original (id number, status varchar2(10), valid varchar2(10)
,primary key (id));
--Create TEMP_TABLE as error log. There will be some extra columns generated.
begin
dbms_errlog.create_error_log(dml_table_name => 'ORIGINAL'
,err_log_table_name => 'TEMP_TABLE');
end;
/
--Test data
insert into original values(1, 't', null);
insert into original values(2, 't', null);
insert into original values(3, 's', null);
commit;
--Update rows in ORIGINAL and also insert those updated rows to TEMP_TABLE.
merge into original original1
using
(
--Duplicate the rows. Only choose rows with the relevant status.
select id, status, valid, rownumber
from original
cross join
(select 1 rownumber from dual union all select 2 rownumber from dual)
where status = 't'
) original2
on (original1.id = original2.id and original2.rownumber = 1)
--Only math half the rows, those with rownumber = 1.
when matched then update set valid = 't'
--The other half will be inserted. Inserting ID causes a PK error and will
--insert the data into the error table, TEMP_TABLE.
when not matched then insert(original1.id, original1.status, original1.valid)
values(original2.id, original2.status, original2.valid)
log errors into temp_table reject limit 999999999;
--Expected: ORIGINAL rows 1 and 2 have VALID = 't'.
--TEMP_TABLE has the two original values for ID 1 and 2.
select * from original;
select * from temp_table;