Merge Statement with two inserts? - sql

Given is a simple MERGE statement. Where I Insert/Update records into traget table. Question: is it possible to also Insert those values in another table with a flag beeing 0 for insert and 1 for update? Eg. when not match do insert into target and another table, when matched do update target and insert into another table.
MERGE dbo.FactBuyingHabits AS Target
USING (SELECT CustomerID, ProductID, PurchaseDate FROM dbo.Purchases) AS Source
ON (Target.ProductID = Source.ProductID AND Target.CustomerID = Source.CustomerID)
WHEN MATCHED THEN
UPDATE SET Target.LastPurchaseDate = Source.PurchaseDate
--and insert into test_tbl values (1, Source.ProductID, Source.CustomerID) --?
WHEN NOT MATCHED BY TARGET THEN
INSERT (CustomerID, ProductID, LastPurchaseDate)
VALUES (Source.CustomerID, Source.ProductID, Source.PurchaseDate)
--and insert into test_tbl values (0, Source.ProductID, Source.CustomerID) --?

you should read about OUTPUT
ex (source);
DECLARE #MergeOutput1 table
(
ActionType nvarchar(10),
BookID int,
OldBookTitle nvarchar(50),
NewBookTitle nvarchar(50),
ModifiedDate datetime
);
-- use MERGE statement to perform update on Book2
MERGE Books2 AS b2
USING Books AS b1
ON (b2.BookID = b1.BookID)
WHEN MATCHED
THEN UPDATE
SET b2.BookTitle = b1.BookTitle
OUTPUT
$action,
INSERTED.BookID,
DELETED.BookTitle,
INSERTED.BookTitle,
INSERTED.ModifiedDate
INTO #MergeOutput1;

Related

Merge not working for insert a record when it's doesn't exist

Can I use Merge to insert a record when it's doesn't exist like below,
MERGE INTO [dbo].[Test] AS [Target]
USING (SELECT DISTINCT [Name] FROM [dbo].[Test]) AS [Source]
ON [Target].[Name] = [Source].[Name]
WHEN NOT MATCHED THEN
INSERT ([Id], [Name])
VALUES (NEWID(), 'Hello');
If the record with value Hello does not exists in table Test, insert it otherwise don't do anything. With above code record is not inserted even I don't have this record in table. And there are no errors.
I know how to accomplish this using insert ... where not exists (...) but am specifically wanting to know how to do it using a merge statement.
The reason your merge statement wasn't working is that you were merging the same table, dbo.Test, back onto itself, so of course there is no missing record.
You can insert a single missing record as follows, where you create a source query to contain the record(s) you wish to insert:
declare #Test table (id uniqueidentifier, [Name] nvarchar(64))
select * from #Test
-- Returns
-- id | Name
-- ----------------------------------------------
MERGE INTO #Test AS [Target]
USING (select 'Hello' [Name]) AS [Source]
ON [Target].[Name] = [Source].[Name]
WHEN NOT MATCHED THEN
INSERT ([Id], [Name])
VALUES (NEWID(), [Name]);
select * from #Test
-- Returns
-- id | Name
-- ----------------------------------------------
-- C1C87CD5-F745-436D-BD8D-55B2AF431BED | Hello
I agree with the answer from Dale K. Its correct.
If I suppose you might have a source_table from where the data needs to get inserted and not to get inserted if the record already exists then you can do the following.
Instead of the MERGE you can
insert
into dbo.Test
(id
,name
)
select top 1
newID()
,'Hello'
from dbo.Test a
where not exists(select 1
from dbo.Test b
where b.name='Hello')

SSIS Staging Table to Normalized form

I could be down the wrong path with this. However, here goes. I am trying to take multiple excel sheets and load them into SQL Server using SSIS.
Excel sheet:
RQ|Descr|PartNum|Manufacturer|...
I am loading this into a staging table with a couple of derived columns:
RQ|Descr|PartNum|Manufacturer|Origin|DateTime|...
This is no big deal, I am able to do this easily. However, the problem is how to get the data from the staging table to the correct table and ensuring FK constraints are followed. See below for an illustration.
My goal is to take RQ|Descr|PartNum|Manufacturer|Origin|DateTime|...
and populate multiple tables
[t1] id|RQ|Descr|Origin|DateTime
[t2] id|t1_id|PartNum|Manufacturer
[t3] id|t1_id|...
I have tried MERGE however I am unsure how to keep the FK relationship.
MERGE INTO spin_item AS targ
USING ssis_stage AS src ON 1=0 -- always generates "not matched by target"
WHEN NOT MATCHED BY TARGET THEN
-- INSERT into spin_item:
INSERT (description, reqqty, price, origin, datetime, exclude, status, siteid, production, repairable)
VALUES (src.description, src.rq, src.price, src.origin, GETDATE(), 0, 'N', '', 0, 0)
-- INSERT into spin_part:
OUTPUT inserted.ID, src.manufacturer, src.partnum
INTO spin_part (ID, src.manufacturer, src.partnum);
I have looked into this SSIS : Using multicast to enter data into 2 RELATED destinations but this is for a one-to-many relationship. So, I am not sure how to populate my t1 table and use the id to populate t2, t3 from the staging table.
EDIT: Below, seems to be a working solution. However, I am not sure that it is a good solution.
BEGIN
SET IDENTITY_INSERT dbo.spin_item ON
--Insert into spin_item
MERGE INTO spin_item AS targ
USING ssis_stage AS src ON 1=0
WHEN NOT MATCHED BY TARGET THEN
INSERT (id, description, reqqty, price, origin, datetime, exclude, status, siteid, production, repairable)
VALUES (src.id, src.description, src.rq, src.price, src.origin, GETDATE(), 0, 'N', '', 0, 0);
SET IDENTITY_INSERT dbo.spin_item OFF
--Insert into spin_part
MERGE INTO spin_part AS targ
USING ssis_stage AS src ON 1=0
WHEN NOT MATCHED BY TARGET AND src.partnum IS NOT NULL THEN
INSERT (itemid_id, manufacturer, partnum, catalognum, [primary])
VALUES (src.id, src.manufacturer, src.partnum, src.partnum, 1);
--Insert into spin_stock
MERGE INTO spin_stock AS targ
USING ssis_stage AS src ON 1=0
WHEN NOT MATCHED BY TARGET AND src.stock IS NOT NULL THEN
INSERT (itemid_id, stocknum)
VALUES (src.id, src.stock);
--Insert into spin_collaboration
MERGE INTO spin_collaboration AS targ
USING ssis_stage AS src ON 1=0
WHEN NOT MATCHED BY TARGET AND src.notes IS NOT NULL THEN
INSERT (itemid_id, comment, datetime)
VALUES (src.id, src.notes, GETDATE());
DELETE FROM ssis_stage WHERE id > 0 --Instead of Truncate since auto_increment will reset.
END
You can create an ID column on your staging table, based off your target tables that is then used as the FK in each table insert:
declare #source table (ID int, a int, b int, c int);
insert into #source values
(null,1,1,1)
,(null,1,1,2)
,(null,1,2,2)
,(null,5,3,2)
,(null,7,1,2)
,(null,2,1,2)
declare #target1 table (ID int, a int);
insert into #target1 values
(1,5)
,(2,6)
,(3,99);
declare #target2 table (ID int, b int, c int);
insert into #target2 values
(1,3,2)
,(2,9,7)
,(3,57,3);
update s
set ID = ss.IDNew
from #source s
inner join (
select row_number() over (order by a,b,c) + (select max(ID) from #target1) as IDNew
,a
,b
,c
from #source
) ss
on(s.a = ss.a
and s.b = ss.b
and s.c = ss.c
);
select * from #target1;
select * from #source;
insert into #target1
select ID
,a
from #source;
insert into #target2
select ID
,b
,c
from #source;
select * from #target1;
select * from #target2;

Insert into a Informix table or update if exists

I want to add a row to an Informix database table, but when a row exists with the same unique key I want to update the row.
I have found a solution for MySQL here which is as follows but I need it for Informix:
INSERT INTO table (id, name, age) VALUES(1, "A", 19) ON DUPLICATE KEY UPDATE name="A", age=19
You probably should use the MERGE statement.
Given a suitable table:
create table table (id serial not null primary key, name varchar(20) not null, age integer not null);
this SQL works:
MERGE INTO table AS dst
USING (SELECT 1 AS id, 'A' AS name, 19 AS age
FROM sysmaster:'informix'.sysdual
) AS src
ON dst.id = src.id
WHEN NOT MATCHED THEN INSERT (dst.id, dst.name, dst.age)
VALUES (src.id, src.name, src.age)
WHEN MATCHED THEN UPDATE SET dst.name = src.name, dst.age = src.age
Informix has interesting rules allowing the use of keywords as identifiers without needing double quotes (indeed, unless you have DELIMIDENT set in the environment, double quotes are simply an alternative to single quotes around strings).
You can try the same behavior using the MERGE statement:
Example, creation of the target table:
CREATE TABLE target
(
id SERIAL PRIMARY KEY CONSTRAINT pk_tst,
name CHAR(1),
age SMALLINT
);
Create a temporary source table and insert the record you want:
CREATE TEMP TABLE source
(
id INT,
name CHAR(1),
age SMALLINT
) WITH NO LOG;
INSERT INTO source (id, name, age) VALUES (1, 'A', 19);
The MERGE would be:
MERGE INTO target AS t
USING source AS s ON t.id = s.id
WHEN MATCHED THEN
UPDATE
SET t.name = s.name, t.age = s.age
WHEN NOT MATCHED THEN
INSERT (id, name, age)
VALUES (s.id, s.name, s.age);
You'll see that the record was inserted then you can:
UPDATE source
SET age = 20
WHERE id = 1;
And test the MERGE again.
Another way to do it is create a stored procedure, basically you will do the INSERT statement and check the SQL error code, if it's -100 you go for the UPDATE.
Something like:
CREATE PROCEDURE sp_insrt_target(v_id INT, v_name CHAR(1), v_age SMALLINT)
ON EXCEPTION IN (-100)
UPDATE target
SET name = v_name, age = v_age
WHERE id = v_id;
END EXCEPTION
INSERT INTO target VALUES (v_id, v_name, v_age);
END PROCEDURE;

How to know that MERGE operation was INSERT or UPDATE?

Let say I have,
MERGE INTO SHARE_AD_GROUP A
USING (
SELECT SHARE_AD_GROUP_ID,
SHARE_ID,
AD_GROUP,
SHARE_PERMISSIONS
FROM SHARE_AD_GROUP
WHERE SHARE_ID = #shareID AND AD_GROUP = #ownerId
) B ON (A.SHARE_AD_GROUP_ID = B.SHARE_AD_GROUP_ID)
WHEN MATCHED THEN
UPDATE SET A.SHARE_PERMISSIONS = B.SHARE_PERMISSIONS
WHEN NOT MATCHED THEN
INSERT (SHARE_PERMISSIONS) VALUES(#sharePermissions);
-- In Here how do I know that it is insert or update
How to know that MERGE operation was INSERT or UPDATE after INSERT OR UPDATE?
Please refer here
DECLARE #SummaryOfChanges TABLE(Change VARCHAR(20));
MERGE tblTarget AS Target
USING (SELECT Col1,Col2 FROM tblSource)
AS Source
ON (Target.Col1 = Source.Col1)
WHEN MATCHED THEN
UPDATE SET target.Col2 = source.Col2 -- Need to get affected rows here
WHEN NOT MATCHED BY TARGET THEN
INSERT (Col1,Col2) VALUES (Col1,Col2); -- Need to get affected rows here
OUTPUT $action INTO #SummaryOfChanges;
SELECT Change, COUNT(*) AS CountPerChange
FROM #SummaryOfChanges
GROUP BY Change;

SQL Server Merge - Getting matched records to another temp table

I have a MERGE query to update data. In case of no match I am inserting records to source getting the output to a temporary table.
Would it be possible to get the matched records to temporary table as well? Basically to avoid duplication of data in further processing I need to have copy of matched records.
This is my MERGE command:
MERGE Product.ProductHeaderRepository AS t
USING (SELECT GETDATE() as d, c1, c2, c3,
Name FROM Supplier.ProductHeaderImport
WHERE (BatchID = #BatchID) ) AS s
ON dbo.GetProductHeaderId(s.c1,S.c2,S.c3) <0
WHEN NOT MATCHED BY TARGET THEN
INSERT (Name, c1,c2,c3) VALUES (Name, c2,c2,c3)
OUTPUT INSERTED.iD, s.c1, s.c2, s.c3 INTO #TmpTable;
You could create a MATCHED clause that does not change anything and just updates a variable, e.g.
DECLARE #T1 TABLE (A INT, B INT);
DECLARE #T2 TABLE (A INT, B INT);
DECLARE #T3 TABLE (Action VARCHAR(20), A INT, B INT);
INSERT #T1 VALUES (1, 1), (2, 2), (3, 3);
INSERT #T2 VALUES (1, 0), (2, NULL), (4, 0);
DECLARE #I INT; -- VARIABLE TO UPDATE
MERGE #T2 B
USING #T1 A
ON A.A = B.A
WHEN MATCHED THEN
UPDATE SET #I = 1 -- DO NOTHING MEANINGFUL IN THE UPDATE;
WHEN NOT MATCHED BY TARGET THEN
INSERT (A, B) VALUES (A.A, A.B)
OUTPUT $action, ISNULL(inserted.A, deleted.A), ISNULL(inserted.B, deleted.B) INTO #T3;
SELECT *
FROM #T3;
Will return:
Action A B
INSERT 3 3
UPDATE 1 0
UPDATE 2 NULL
So if you add a new column to #TmpTable to store the action you can get your matched rows using:
SELECT *
FROM #TmpTable
WHERE Action = 'UPDATE';
And your new rows using:
SELECT *
FROM #TmpTable
WHERE Action = 'INSERT';