Merge Into with no rows - sql-server

Is there a way to use the instruction:
MERGE INTO MySchema.MyTable AS Target
USING (VALUES
........
)
With nothing instead of the dots? Usually you have there something like a list of (firstValue, SecondValue,...,LastValue), one for each row you want to merge but I'd like to be able to write the instruction with NO rows so that the DELETE part of the MERGE deletes all the rows.
This is because I am using a stored procedure that creates the MERGE instruction automatically but sometimes the table that i am starting from is empty.
Of course I tried with:
MERGE INTO MySchema.MyTable AS Target USING (VALUES)
but it is not accepted.
Example:
MERGE INTO [dbo].[MyTable] AS Target
USING (VALUES (1,'Attivo') ,(2,'Disabilitato') ,(3,'Bloccato') ) AS Source ([IDMyField],[MyField]) ON (Target.[IDMyField] = Source.[IDMyField])
WHEN MATCHED AND ( NULLIF(Source.[MyField], Target.[MyField]) IS NOT NULL OR NULLIF(Target.[MyField], Source.[MyField]) IS NOT NULL)
THEN UPDATE SET [MyField] = Source.[MyField]
WHEN NOT MATCHED BY TARGET
THEN INSERT([IDMyField],[MyField]) VALUES(Source.[IDMyField],Source.[MyField])
WHEN NOT MATCHED BY SOURCE
THEN DELETE;

A viable solution is :
USING (SELECT * FROM MyTable WHERE 1 = 0)

If you're generating the inside query, and the outside query is matching on an predefined ID field, the following will work:
MERGE INTO tester AS Target
USING (
select null as test1 --generate select null, alias as your id field
) as SOURCE on target.test1 = source.test1
WHEN NOT MATCHED BY SOURCE
THEN DELETE;
For your particluar case:
MERGE INTO table1 AS Target
USING (
values(null)
) as SOURCE(id) on target.id = source.id
WHEN NOT MATCHED BY SOURCE
THEN DELETE;

Related

IF EXISTS and MERGE Statement

I have data flowing into one table from multiple other tables lets say: Table_A
Then I have a Merge stored proc that takes data from table A merges it with Table B.
However, something doesn't seem to be right. If i truncate and load the data it works fine, but if i dont truncate and load, and just fetch the query by eachh hour I get the error message saying
Msg 8672, Level 16, State 1, Procedure Merge_Table_A, Line 4 [Batch Start Line 0]
The MERGE statement attempted to UPDATE or DELETE the same row more than once. This happens when a target row matches more than one source row. A MERGE statement cannot UPDATE/DELETE the same row of the target table multiple times. Refine the ON clause to ensure a target row matches at most one source row, or use the GROUP BY clause to group the source rows.
How can I overcome this?
I want to be able to incrementally load the data and not do truncate loads, but at the same time have a stored proc that updates or inserts or doesnt care if the row already exists.
Seems you have duplicate rows in your target table which are loaded from your previous runs.
Note: Matching in a Merge does not consider the rows inserted (even duplicate) while running the Merge itself.
Below is my repro example with a sample data:
Table1: Initial data
Table2: Taget table
Merge Statement:
MERGE tb2 AS Target
USING tb1 AS Source
ON Source.firstname = Target.firstname and
Source.lastname = Target.lastname
-- For Inserts
WHEN NOT MATCHED BY Target THEN
INSERT (firstname, lastname, updated_date)
VALUES (Source.firstname, Source.lastname, source.updated_date)
-- For Updates
WHEN MATCHED THEN UPDATE SET
Target.updated_date = Source.updated_date
-- For Deletes
WHEN NOT MATCHED BY Source THEN
DELETE;
When Merge is executed, it inserts all data without any errors.
New data in tb1:
When I run the Merge statement, it gives me the same error as yours.
As a workaround using one of the below options,
Add additional conditions if possible in the ON clause to uniquely identify the data.
Remove the duplicates from the source and merge the data into tb2 as below.
--temp table
drop table if exists #tb1;
select * into #tb1 from (
select *, row_number() over(partition by firstname, lastname order by firstname, lastname, updated_date desc) as rn from tb1) a
where rn = 1
MERGE tb2 AS Target
USING #tb1 AS Source
ON Source.firstname = Target.firstname and
Source.lastname = Target.lastname
-- For Inserts
WHEN NOT MATCHED BY Target THEN
INSERT (firstname, lastname, updated_date)
VALUES (Source.firstname, Source.lastname, source.updated_date)
-- For Updates
WHEN MATCHED THEN UPDATE SET
Target.updated_date = Source.updated_date
-- For Deletes
WHEN NOT MATCHED BY Source THEN
DELETE;
Data merged into tb2 successfully.

MERGE into first table based on id of second table if not matched

I have two tables table_1 and table_2. After inserting some data into table_1(insert not in the example below), it gives Auto_Increment ID to table_1. Then I need to put this new generated ID into table_2 in NOT MATCHED section.
I am trying to use T-SQL's MERGE, to UPDATE table if data already exists (if matched) or INSERT INTO table if there is no such data(not matched), but in second case insert by using one column selected from another table.
Here is what I have already tried:
MERGE
INTO table_2 WITH (HOLDLOCK) AS target
USING (SELECT
'42' AS person_id
,2 AS skill_id
) AS source
(person_id,skill_id )
ON (target.person_id = source.person_id
AND target.skill_id = source.skill_id)
WHEN MATCHED
THEN UPDATE
SET skill_lvl=4,already_have=0
WHEN NOT MATCHED
--section below doesn't work,because insert inside MERGE has to be without select (?)
THEN INSERT (person_id, skill_id, skill_lvl,already_have)
SELECT 42, id,3,1 FROM table_1;
Not matched section gives me an error that he waits values or default, but it seems kind of tricky to select with values or default.
Edit_1
Insert query to table_1 (happens before previous MERGE. Both MERGES within one loop):
MERGE
INTO table_1 WITH (HOLDLOCK) AS target
USING (SELECT
'skill_1' AS skill_name
) AS source
(skill_name)
ON (target.skill_name = source.skill_name)
WHEN NOT MATCHED
THEN INSERT (category_id,skill_name) values (0,'skill_1');
this query in the loop, compares skill_names, if name is not inside this table_1, it inserts this value. Then compare next skill_name and so on. ID's are generating automatically after inserting.

Use Case in SQL for insert or update

I have temporary table that contains multiple rows and original table with below structure :
temp : desc,id
Original :name,id,rate
I have to check if id of one row matches the original one then i have to update the rate in Original else i have to insert row in original and this all should be happening in a loop as we have multiple records in temporary table.
I am working in SQL SERVER 2008
You don't need a loop; you can do this with an update followed by an insert:
UPDATE Original
SET rate = /* whatever the new rate is */
FROM Original
INNER JOIN temp on Original.id = temp.id
INSERT INTO Original (name, id, rate)
SELECT desc, id, /* whatever the rate is */ FROM temp
WHERE id NOT IN (SELECT id FROM Original)
Looks like MERGE can be a good option for you :
-- MERGE statement with the join conditions specified correctly.
USE tempdb;
GO
BEGIN TRAN;
MERGE Target AS T
USING Source AS S
ON (T.EmployeeID = S.EmployeeID)
WHEN NOT MATCHED BY TARGET AND S.EmployeeName LIKE 'S%'
THEN INSERT(EmployeeID, EmployeeName) VALUES(S.EmployeeID, S.EmployeeName)
WHEN MATCHED
THEN UPDATE SET T.EmployeeName = S.EmployeeName
WHEN NOT MATCHED BY SOURCE AND T.EmployeeName LIKE 'S%'
THEN DELETE
OUTPUT $action, inserted.*, deleted.*;
ROLLBACK TRAN;
GO
Straight pick from below link
https://technet.microsoft.com/en-us/library/bb522522(v=sql.105).aspx

Moving data between fields in destination table in T-SQL MERGE

I have a SQL MERGE statement that does an UPDATE if a value exists (and an INSERT if it does). I'd like to record the last few "historical" values in the row when an UPDATE happens, sort of like this:
MERGE into desttable as dest
USING sourcetable as src
on src.ref = dest.ref
WHEN MATCHED THEN
UPDATE SET dest.oldvalue3=dest.oldvalue2,
dest.oldvalue2=dest.oldvalue1,
dest.oldvalue1=dest.value,
dest.value=src.newvalue
WHEN NOT MATCHED THEN
INSERT ...
... so essentially, the existing "value" is shuffled down to "oldvalue1", "oldvalue1" becomes "oldvalue2" etc. However, when I run this, every column gets set to "value" - the UPDATE statement is obviously setting all the fields at once. Is there any way to achieve what I'm trying to do?
You can left outer join desttable in the using clause and use columns from desttable as source to the update.
MERGE INTO desttable AS dest
USING (SELECT s.ref,
s.newvalue,
d.oldvalue2,
d.oldvalue1,
d.value
FROM sourcetable AS s
LEFT OUTER JOIN desttable AS d
ON s.ref = d.ref) AS src
ON src.ref = dest.ref
WHEN MATCHED THEN
UPDATE SET dest.oldvalue3=src.oldvalue2,
dest.oldvalue2=src.oldvalue1,
dest.oldvalue1=src.value,
dest.value=src.newvalue
;
https://data.stackexchange.com/stackoverflow/q/114412/

Merge query in SQL Server 2008

I having the scenario of loading the data from source table to target table. If the data from source is not present in target, then i need to insert. If it is present in the target table already, then update the status of the row to 'expire' and insert the column as new row. I used Merge query to do this. I can do insert if not exists and i can do update also. But while trying to insert when matched, it says insert not allowed in 'when matched' clause.
Please help me.. Thanks in advance
If you want to perform multiple actions for a single row of source data, you need to duplicate that row somehow.
Something like the following (making up table names, etc):
;WITH Source as (
SELECT Col1,Col2,Col3,t.Dupl
FROM SourceTable,(select 0 union all select 1) t(Dupl)
)
MERGE INTO Target t
USING Source s ON t.Col1 = s.Col1 and s.Dupl=0 /* Key columns here */
WHEN MATCHED THEN UPDATE SET Expired = 1
WHEN NOT MATCHED AND s.Dupl=1 THEN INSERT (Col1,Col2,Col3) VALUES (s.Col1,s.Col2,s.Col3);
You always want the s.Dupl condition in the not matched branch, because otherwise source rows which don't match any target rows would be inserted twice.
From the example you posted as a comment, I'd change:
MERGE target AS tar USING source AS src ON src.id = tar.id
WHEN MATCHED THEN UPDATE SET D_VALID_TO=#nowdate-1, C_IS_ACTIVE='N', D_LAST_UPDATED_DATE=#nowdate
WHEN NOT MATCHED THEN INSERT (col1,col2,col3) VALUES (tar.col1,tar.col2,tar.col3);
into:
;WITH SourceDupl AS (
SELECT id,col1,col2,col3,t.Dupl
FROM source,(select 0 union all select 1) t(Dupl)
)
MERGE target AS tar USING SourceDupl as src on src.id = tar.id AND Dupl=0
WHEN MATCHED THEN UPDATE SET D_VALID_TO=#nowdate-1, C_IS_ACTIVE='N', D_LAST_UPDATED_DATE=#nowdate
WHEN NOT MATCHED AND Dupl=1 THEN INSERT (col1,col2,col3) VALUES (src.col1,src.col2,src.col3);
I've changed the values in the VALUES clause, since in a NOT MATCHED branch, the tar table doesn't have a row to select values from.
Check out one of those many links:
Using SQL Server 2008's MERGE Statement
MERGE on Technet
Introduction to MERGE statement
SQL Server 2008 MERGE
Without actually knowing what your database tables look like, we cannot be of more help - you need to read those articles and figure out yourself how to apply this to your concrete situation.

Resources