SQL MERGE, Deleting each item from source after update or insert - sql

I have a temporary table which I use to insert many data into rapidly . Then I use a SQL merge to insert data from temp table into main table(Target Table). But I want to delete each record from source table as soon as inserted into target table. Something like the code below:
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)
--THEN DELETE INSERTED ITEM------------------------------
WHEN MATCHED
THEN UPDATE SET T.EmployeeName = S.EmployeeName
WHEN NOT MATCHED BY SOURCE AND T.EmployeeName LIKE 'S%'
THEN DELETE

I want to delete each record from source table as soon as inserted
into target table.
In the RDBMS for most cases you can rephrase this as
I want to delete records from source table in the same transaction with INSERT/UPDATE
so I suggest is approach:
BEGIN TRANSACTION
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)
--THEN DELETE INSERTED ITEM------------------------------
WHEN MATCHED
THEN UPDATE SET T.EmployeeName = S.EmployeeName
WHEN NOT MATCHED BY SOURCE AND T.EmployeeName LIKE 'S%'
THEN DELETE
OUTPUT inserted.EmployeeID INTO #TempTable
DELETE FROM Source
WHERE EmployeeID IN (SELECT EmployeeID FROM #TempTable)
COMMIT TRANSACTION

Like the documentation says, merge "Performs insert, update, or delete operations on a target table based on the results of a join with a source table".
Can't you just add a delete statement after the merge to delete rows from the original table that exists in the temp table?

Related

SQL Merge Into with multiple statements

I'm trying to keep a historic table of another one. When updating the original I would like to insert rows into the historic one.
I'm using Sql Merge:
MERGE TargetProducts AS Target
USING SourceProducts AS Source
ON Source.ProductID = Target.ProductID
-- For Inserts
WHEN NOT MATCHED BY Target THEN
INSERT (ProductID,ProductName, Price)
VALUES (Source.ProductID,Source.ProductName, Source.Price)
-- For Updates
WHEN MATCHED THEN UPDATE SET
Target.ProductName = Source.ProductName,
Target.Price = Source.Price
-- For Deletes
WHEN NOT MATCHED BY Source THEN
DELETE;
Can I make multiple statements in the same "when" condition?, as such:
...
-- For Inserts
WHEN NOT MATCHED BY Target THEN
INSERT (ProductID,ProductName, Price) VALUES (Source.ProductID,Source.ProductName, Source.Price);
INSERT INTO anotherTable (OldProductID,OldProductName, OldPrice) VALUES (Source.ProductID,Source.ProductName, Source.Price);
...
Normally you can INSERT only into one table. The syntax does not allow multiple statements in the same "when" condition.
But, SQL Server has OUTPUT clause which allows to add another table. It is very handy when you need to have some sort of auditing trail.
See this question How to INSERT into multiple tables from one SELECT statement
So, add OUTPUT clause to your MERGE statement. Something like this:
MERGE TargetProducts AS Target
USING SourceProducts AS Source
ON Source.ProductID = Target.ProductID
-- For Inserts
WHEN NOT MATCHED BY Target THEN
INSERT (ProductID,ProductName, Price)
VALUES (Source.ProductID,Source.ProductName, Source.Price)
-- For Updates
WHEN MATCHED THEN UPDATE SET
Target.ProductName = Source.ProductName,
Target.Price = Source.Price
-- For Deletes
WHEN NOT MATCHED BY Source THEN
DELETE
OUTPUT inserted.ProductID, inserted.ProductName, inserted.Price
INTO anotherTable (OldProductID,OldProductName, OldPrice)
;
This will capture both updates and inserts in anotherTable. To capture only inserts you can output at first into a temp table and then filter results by MERGE $action.
Have a look at this question:
Pipes and filters at DBMS-level: Splitting the MERGE output stream

Is it possible to MERGE the OUTPUT of a DELETE query in SQL?

I have two identically-shaped tables: an "active" table, and an "archive" table. As part of a recurring process, I want to move rows from the active to the archive, and I'd like to do it using MERGE so that my process doesn't fail with a Violation of PRIMARY KEY constraint '...'. Cannot insert duplicate key....
I had thought to do it like so:
MERGE INTO Archive
USING (DELETE FROM Active OUTPUT DELETED.* WHERE Active.Id IN (...)) AS Active
ON (Active.Id = Archive.Id)
WHEN NOT MATCHED THEN
INSERT (Id, ...)
VALUES (Active.Id, ...);
but then learned that A nested INSERT, UPDATE, DELETE, or MERGE statement is not allowed in the USING clause of a MERGE statement.
The only other thing that occurs to me is to use a temp table:
SELECT TOP 0 * INTO #temp FROM Active
DELETE FROM Active
OUTPUT DELETED.* INTO #temp
WHERE Id IN (...)
MERGE INTO Archive
USING #temp AS Active
ON (Active.Id = Archive.Id)
WHEN NOT MATCHED THEN
INSERT (Id, ...)
VALUES (Active.Id, ...);
and it works, but it feels... unsatisfying.
Is there a more concise or direct way to achieve this "safe row movement"?

Filtering duplicate records while importing data from source table to target table

This may be a simple query to some of you. But I am not strong in Sql, so expecting some solution for my problem.
I have 2 tables, ProductVenueImport and SupplierVenueImport.
We are dumping all the records from SupplierVenueImport to ProductVenueImport using MERGE clause and a Temp table. Temp will have valid records from SupplerVenuImport and from Temp table we are importing records to ProductVenueImport.
But before importing data to ProductVenueImport from Temp table I need to check for the duplicate records in my target (ProductVenueImport).
For example if I am importing a record with name as 'A', I need to look into ProductVenueImport whether 'A' already existing or not. If it is not existing then only I need to insert 'A' otherwise not.
Could somebody tell me how to do this?
Is using Cursors only the option?
Thanks,
Naresh
Assuming the Temp table itself doesn't have duplicates, you could use MERGE like this:
Insert non-existing products.
Do a NO-OP in case of an existing product.
Use $action in the OUTPUT clause to mark which rows were considered for insertion (and inserted) and which for update (but not really updated).
This is what I mean:
DECLARE #noop int; -- needed for the NO-OP below
MERGE INTO ProductVenueImport AS tgt
USING Temp AS src
ON src.ProductID = tgt.ProdutID
WHEN NOT MATCHED THEN
INSERT ( column1, column2, ...)
VALUES (src.column1, src.column2, ...)
WHEN MATCHED THEN
UPDATE SET #noop = #noop -- the NO-OP instead of update
OUTPUT $action, src.column1, src.column2, ...
INTO anotherTempTable
;
I think this would do this :
INSERT INTO PRODUCTTBL(FEILD1, FIELD2, FIELD3, FIELD4, FIELD5)
SELECT (FIELD1,FIELD2,FIELD3,FIELD4,FIELD5) FROM TEMP WHERE CRITERIAFIELD NOT IN(SELECT DISTINCT CRITERIAFIELD FROM PRODUCTTBL)
This should allow you to check for duplicates in a table
select columnname from tablename
group by columnname
having count(columnname) >1
sorry if I am not getting the question right, can't you use the merge statement on the source table with "When not matched Insert" to insert the new records alone
so in your case it should be like this
merge into ProductVenueImport using temp on (<condition for duplicate>)
when not matched then insert <clause>;
the merge clause will make sure that no duplicate records are inserted into your source table.

Synchronizing 2 tables with MERGE

I’ve been tasked to synchronize 2 tables (both are identical). They have 60 columns each. Table A is the primary table that will be initially filled. I need to create a stored procedure (done) that will merge these 2 tables and populate both with the same exact data (Update, insert, delete) when called. How would I use the MERGE function in SQL to achieve this? I’ve looked at both the MSDN documentation and similar that’s on technet, but I’m pretty confused on getting started. Do I need to specify each field I need merged? Or is it a simple call I’m missing that will perform this action?
Here is a link to a simple example of the MERGE statement:
http://www.simple-talk.com/sql/learn-sql-server/the-merge-statement-in-sql-server-2008/
The basic syntax reads as:
MERGE table1
USING table2
ON table1.id = table2.id
WHEN MATCHED THEN
--Do an update here
WHEN NOT MATCHED BY TARGET THEN
--Do an insert here (or a delete)
;
You can also use WHEN NOT MATCHED BY SOURCE
Over 60 columns is a great number! When I need to sync 2 identical table I do:
;WITH tbl_to_synch as (
-- Prepare table to update,
Select *,chk = CHECKSUM(*) from [dbo].[tableA]
)
MERGE tbl_to_synch as [Target]
USING (Select *,chk = CHECKSUM(*) from [dbo].[tableB]) as [source]
ON [Target].key = [source].key
WHEN MATCHED AND [Target].chk <> [source].chk THEN
-- UPDATE ONLY row that is changed
UPDATE
SET
column01 = [source].[column01]
,column02 = [source].[column01]
-- ....
,column59 = [source].[column59]
,column60 = [source].[column59]
WHEN NOT MATCHED BY TARGET THEN
insert (column01, column02, ...,column59,column60)
values (column01, column02, ...,column59,column60)
WHEN NOT MATCHED BY SOURCE THEN DELETE
-- Show what is changed
OUTPUT $action, ISNULL(INSERTED.key,DELETED.key);

How to use update trigger to update another table?

I am new to triggers and want to create a trigger on an update of a column and update another table with that value.
I have table1 with a year column and if the application updates that year column I need to update table 2 with the year the same year.
ALTER TRIGGER [dbo].[trig_UpdateAnnualYear]
ON [dbo].[table1]
AFTER UPDATE
AS
if (UPDATE (intAnnualYear))
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for trigger here
Update table2 set AnnualYear = intAnnualYear where table2.ID = table1.ID
END
You don't reference table1 inside the trigger. Use the inserted pseudo table to get the "after" values. Also remember that an update can affect multiple rows.
So replace your current update statement with
UPDATE table2
SET table2.annualyear = inserted.intannualyear
FROM table2
JOIN inserted
ON table2.id = inserted.id
You only need to update the records in table2 if the column intannualyear is involved. Also, this is an alternative UPDATE syntax across two tables from what Martin has shown
IF UPDATE(intannualyear)
UPDATE table2
SET annualyear = inserted.intannualyear
FROM inserted
WHERE table2.id = inserted.id
According to this question, if there's only one "downstream" table then another option with a properly defined foreign key relation would be Cascaded update.
To supplement the above answers, if you have to check more than one column you can use a INNER JOIN between inserted and deleted, or several UPDATE() calls:
IF ( UPDATE(Col1) OR UPDATE(Col2) ) BEGIN ...