Is it possible to MERGE the OUTPUT of a DELETE query in SQL? - 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"?

Related

Insert bulk data into two related tables with foreign keys from another table

I have imported some data to a temp SQL table from an Excel file. Then I have tried to insert all rows to two related tables. Simply like this: There are Events and Actors tables with many to many relationship in my database. Actors are already added. I want to add all events to Events table and then add relation(ActorId) for each event to EventActors tables.
(dbo.TempTable has Title, ActorId columns)
insert into dbo.Event (Title)
Select Title
From dbo.TempTable
insert into dbo.EventActor (EventId, ActorId)
Select SCOPE_IDENTITY(), ActorId --SCOPE_IDENTITY() is for EventId
From dbo.TempTable
When this code ran, all events inserted into Events, but the relations didn't inserted into EventActors because of Foreign Key error.
I think there should be a loop. But I am confused. I don't want to write C# code for this. I know there would be a simple but advanced solution trick for this in SQL Server. Thanks for your help.
Use the output clause to capture the new IDs, with a merge statement to allow capture from both source and destination tables.
Having captured this information, join it back to the temp table for the second insert.
Note you need a unique id per row, and this assumes 1 row in the temp table creates 1 row in both the Event and the EventActor tables.
-- Ensure every row has a unique id - could be part of the table create
ALTER TABLE dbo.TempTable ADD id INT IDENTITY(1,1);
-- Create table variable for storing the new IDs in
DECLARE #NewId TABLE (INT id, INT EventId);
-- Use Merge to Insert with Output to allow us to access all tables involves
-- As Insert with Output only allows access to columns in the destination table
MERGE INTO dbo.[Event] AS Target
USING dbo.TempTable AS Source
ON 1 = 0 -- Force an insert regardless
WHEN NOT MATCHED THEN
INSERT (Title)
VALUES (Source.Title)
OUTPUT Source.id, Inserted.EventId
INTO #NewId (id, EventId);
-- Insert using new Ids just created
INSERT INTO dbo.EventActor (EventId, ActorId)
SELECT I.EventId, T.ActorId
FROM dbo.TempTable T
INNER JOIN #NewId I on T.id = T.id;

Oracle set based insert vs set based merge performance

We're using Oracle 11g at the moment without Enterprise (not an option unfortunately).
Let's say I have a table with a constant(Let's say 2000) rows of data. Let's call it data_source.
I want to insert some columns of this table into another table, data_dest. I'm using all the records from the source table.
In other words, I would like to insert this set
select data_source.col1, data_source.col2, ... data_source.colN
from data_source
Which would be faster in this case:
insert into data_dest
select data_source.col1, data_source.col2, ... data_source.colN
from data_source
OR
merge into data_dest dd
using data_source ds
on (dd.col1 = ds.col1) --Let's assume that this is a matching column names
when not matched
insert (col1,col2...)
values(ds.col1,ds.col2...)
EDIT 1:
We can assume there are no primary keys violations from the insert.
In other words we can assume that insert will successfully insert all of the rows and so will merge.
The insert is very likely faster because it does not require a join on the two tables.
That said, the two queries are not equivalent. Assuming that col1 is defined as the primary key, the insert will throw an error if data_source contains a value in col1 that is already in data_dest. Because the merge is comparing the data in the two tables, then only inserting only the rows that don't already exist, it won't ever throw a primary key violation.
An insert that would be equivalent to the merge would be:
INSERT INTO data_dest
SELECT data_source.col1, data_source.col2, ... data_source.colN
FROM data_source
WHERE NOT EXISTS
(SELECT *
FROM data_dest
WHERE data_source.col1 = data_dest.col1)
It's likely that the plan for this insert will be very similar (if not identical) to the plan for the merge and the performance would be indistinguishable.

SQL Merge - Persist data

I use SQL Server 2014.
In my procedure, I have a MERGE statement and I have a question about it.
My MERGE statement has simple following structure:
MERGE dbo.T1 AS tgt
USING (SELECT ...) AS src ON ...
WHEN MATCHED THEN
UPDATE ...
WHEN NOT MATCHED THEN
INSERT ...
OUTPUT inserted.MyColumn
INTO #NewTable (MyColumnValue);
Just like how it populates a table for all inserts, I also need it to populate another table for all updates too.
Is is possible, and if yes then would you please let me know how?
No, it's not possible to direct the results to two tables. See this question.
You can make the table wider and output both the inserted and deleted columns on the same row:
MERGE dbo.T1 AS tgt
USING (SELECT ...) AS src ON ...
WHEN MATCHED THEN
UPDATE ...
WHEN NOT MATCHED THEN
INSERT ...
OUTPUT $action, inserted.col1, inserted.col2, deleted.col1, deleted.col2
INTO #NewTable (action, inserted_col1, inserted_col2, deleted_col1, deleted_col2);
Then you can split #NewTable however you want.

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.

MERGE Violation of PRIMARY KEY constraint

I have a SQL Server 2008 many-to-many relationship table (Assets) with two columns:
AssetId (PK, FK, uniqueidentifier, not null)
AssetCategoryId (PK, FK, int, not null)
In my project, I need to take rows from this table, and insert them into a replicated database periodically. So, I have two databases that are exactly the same (constraints included).
In order to "copy" from one database to the other, I use a MERGE statement with a temp table. I insert up to 50 records into the temp table, then merge the temp table with the Assets table I am copying into as follows:
CREATE TABLE #Assets (AssetId UniqueIdentifier, AssetCategoryId Int);
INSERT INTO #Assets (AssetId, AssetCategoryId) VALUES ('ed05bac3-7a92-46aa-8822-2d882b137597', 44), ('dc5e3082-e2eb-4bdf-a640-94e0f59411ed', 22) ... ;
MERGE INTO Assets WITH (HOLDLOCK) AS Target
USING #Assets AS Source
ON Target.AssetId = Source.AssetId AND Target.AssetCategoryId = Source.AssetCategoryId
WHEN MATCHED THEN
UPDATE SET ...
WHEN NOT MATCHED BY Target THEN
INSERT (AssetId,AssetCategoryId) VALUES (Source.AssetId,Source.AssetCategoryId);
This works great, for the most part. However, once in a while, I get the error:
Violation of PRIMARY KEY constraint 'PK_Assets'. Cannot insert
duplicate key in object 'dbo.Assets'. The duplicate key value is
(dc5e3082-e2eb-4bdf-a640-94e0f59411ed, 22). The statement has been
terminated.
When I check in the Assets table, no such record exists... so I am confused how I would be inserting a duplicate key.
Any idea what is going on here?
UPDATE
When testing, it runs successfully 6 times, inserting 300 rows. On the 7th try, it always gives the same error shown above. Furthermore, when I INSERT (dc5e3082-e2eb-4bdf-a640-94e0f59411ed, 22) by itself, it works fine. My test is then able to continue and insert the remaining rows with no errors.
You need to add a HOLDLOCK on your MERGE statement. Try the following:
MERGE INTO Assets WITH (HOLDLOCK) AS Target
...
This avoids the race condition that you are running into. See more info here
EDIT
Based on your update, the only other thing I can think of is that your temp table might have a duplicate record in it. Can you double check?