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
Related
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"?
Is there a way in Bigquery to combine DELETE and INSERT statements into one
DELETE `my_project.my_dataset.demo`
WHERE date = CURRENT_DATE()
INSERT INTO `my_project.my_dataset.demo`
SELECT * FROM `my_project.my_dataset.my_source`
WHERE date = CURRENT_DATE()
Any statement that can combine the above two DML into one ?
MERGE:
https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement
A MERGE statement is a DML statement that can combine INSERT, UPDATE, and DELETE operations into a single statement and perform the operations atomically.
In the following example, all of the products in the NewArrivals table are replaced with values from the subquery. The INSERT clause does not specify column names for either the target table or the source subquery.
MERGE dataset.NewArrivals
USING (SELECT * FROM UNNEST([('microwave', 10, 'warehouse #1'),
('dryer', 30, 'warehouse #1'),
('oven', 20, 'warehouse #2')]))
ON FALSE
WHEN NOT MATCHED THEN
INSERT ROW
WHEN NOT MATCHED BY SOURCE THEN
DELETE
I'm slightly modifying Felipe's answer to handle this use case. The only thing that needs to be changed is to add an extra clause to the WHEN NOT MATCHED statement:
MERGE `my_project.my_dataset.demo`
USING (SELECT * from `my_project.my_dataset.my_source` WHERE date=CURRENT_DATE())
ON 1=2 /* exactly the same as ON FALSE, but slightly clearer */
WHEN NOT MATCHED BY SOURCE AND date=CURRENT_DATE() THEN
DELETE
WHEN NOT MATCHED BY TARGET THEN
INSERT ROW
The key to understanding what's going on is that we're matching on the condition 1=2 -- that is, we will never match. But we can add extra conditions to our NOT MATCHED clause.
WHEN NOT MATCHED BY SOURCE is every record in the existing table.
WHEN NOT MATCHED BY SOURCE AND (whatever) is every record in the existing table that matches the "whatever" clause
WHEN NOT MATCHED BY TARGET is every record in the incoming data
If you aim to delete today's data from the table my_project.my_dataset.demo and replace them with today's data coming from my_project.my_dataset.my_source, you can recreate my_project.my_dataset.demo using CREATE OR REPLACE TABLE as a result from UNION ALL
CREATE OR REPLACE TABLE `my_project.my_dataset.demo`
AS
SELECT * FROM `my_project.my_dataset.demo`
WHERE date != CURRENT_DATE()
UNION ALL
SELECT * FROM `my_project.my_dataset.my_source`
WHERE date = CURRENT_DATE()
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?
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.
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);