How to retrieve old values in OUTPUT clause with SQL MERGE statement - sql-server

I'm using MERGE statement to update a product table containing (Name="a", Description="desca"). My source table contains (Name="a", Description="newdesca") and I merge on the Name field.
In my Output clause, I would like to get back the field BEFORE the update -> Description = "desca".
I couldn't find a way to do that, I'm always getting back the new value ("newdesca"). Why?

Can you not just used the deleted memory-resident table. e.g:
IF OBJECT_ID(N'tempdb..#T', 'U') IS NOT NULL
DROP TABLE #T;
CREATE TABLE #T (Name VARCHAR(5), Description VARCHAR(20));
INSERT #T (Name, Description)
VALUES ('a', 'desca'), ('b', 'delete');
MERGE #T AS t
USING (VALUES ('a', 'newdesca'), ('c', 'insert')) AS m (Name, Description)
ON t.Name = m.Name
WHEN MATCHED THEN
UPDATE SET Description = m.Description
WHEN NOT MATCHED BY TARGET THEN
INSERT (Name, Description)
VALUES (m.Name, m.Description)
WHEN NOT MATCHED BY SOURCE THEN
DELETE
OUTPUT $Action, inserted.*, deleted.*;
IF OBJECT_ID(N'tempdb..#T', 'U') IS NOT NULL
DROP TABLE #T;
The output of this would be:
$Action | Name | Description | Name | Description
--------+-------+-------------+------+--------------
INSERT | c | insert | NULL | NULL
UPDATE | a | newdesca | a | desca
DELETE | NULL | NULL | b | delete

Related

Snowflake does not implement the full SQL MERGE statement?

I am trying to create a Snowflake task that executes a MERGE statement.
However, it seems that Snowflake does not recognize the “when not matched by target” or “when not matched by source” statements.
create or replace task MERGE_TEAM_TOUCHPOINT
warehouse = COMPUTE_WH
schedule = '1 minute'
when system$stream_has_data('TEAMTOUCHPOINT_CDC')
as
merge into dv.Team_Touchpoint as f using TeamTouchpoint_CDC as s
on s.uniqueid = f.uniqueid
when matched then
update set TEAMUNIQUEID = s.TEAMUNIQUEID,
TOUCHPOINTUNIQUEID = s.TOUCHPOINTUNIQUEID
when not matched by target then
insert (
ID,
UniqueID,
TEAMUNIQUEID,
TOUCHPOINTUNIQUEID
)
values (
s.ID,
s.UniqueID,
s.TEAMUNIQUEID,
s.TOUCHPOINTUNIQUEID
)
when not matched by source then delete;
How can I do this? Is there really no other way than creating a stored procedure in javascript to first truncate the table and then insert everything from the staging table?
A workaround suggested by a teammate:
Define MATCHED_BY_SOURCE based on a full join, then look if a.col or b.col are null:
merge into TARGET t
using (
select <COLUMN_LIST>,
iff(a.COL is null, 'NOT_MATCHED_BY_SOURCE', 'MATCHED_BY_SOURCE') SOURCE_MATCH,
iff(b.COL is null, 'NOT_MATCHED_BY_TARGET', 'MATCHED_BY_TARGET') TARGET_MATCH
from SOURCE a
full join TARGET b
on a.COL = b.COL
) s
on s.COL = t.COL
when matched and s.SOURCE_MATCH = 'NOT_MATCHED_BY_SOURCE' then
<DO_SOMETHING>
when matched and s.TARGET_MATCH = 'NOT_MATCHED_BY_TARGET' then
<DO_SOMETHING_ELSE>
;
(same as in https://stackoverflow.com/a/69095225/132438)
Neither 'by target' nor 'by source' are valid keywords within the MERGE command of Snowflake and the Matching is meant to be 'by target' only (https://docs.snowflake.com/en/sql-reference/sql/merge.html). To achieve your goal you need to run the DELETE separately from the MERGE - in which you will be able to run the UPDATE (when MATCHED) and the INSERT (when NOT MATCHED "by target"), as in fact the DELETE can be handled by the MERGE only WHEN MATCHED "by target".
You could handle the two steps (1.DELETE; 2.MERGE-UPDATE&INSERT) within a single explicit transaction in a Stored Procedure, or two different transactions via two separate Tasks, one of which being an AFTER Task.
Alternatively, you can run an INSERT with the optional parameter OVERWRITE which will run a TRUNCATE of the target table and a subsequent loading from the source table, all in a single transaction:
https://docs.snowflake.com/en/sql-reference/sql/insert.html#optional-parameters
Here is a reproducible example of the DELETE + MERGE(UPDATE&INSERT) approach:
USE DEV;
CREATE OR REPLACE TEMPORARY TABLE Public.My_Merge_Target (
Id INTEGER, Name VARCHAR
)
AS
SELECT column1, column2
FROM (VALUES (1, 'Stay as is'), (2, 'This name has to change'), (3, 'This needs to go'));
CREATE OR REPLACE TEMPORARY TABLE Public.My_Merge_Source (
Id INTEGER, Name VARCHAR
)
AS
SELECT column1, column2
FROM (VALUES (1, 'Stay as is'), (2, 'This is the new name for id=2'), (4, 'A new row'));
SELECT * FROM Public.My_Merge_Target ORDER BY Id;
/*
------------------------------------
Id | Name
------------------------------------
1 | Stay as is
2 | This name has to change
3 | This needs to go
*/
SELECT * FROM Public.My_Merge_Source ORDER BY Id;
/*
------------------------------------
Id | Name
------------------------------------
1 | Stay as is
2 | This is the new name for id=2
4 | A new row
*/
DELETE FROM Public.My_Merge_Target AS trg
USING (
SELECT t.Id FROM Public.My_Merge_Source AS s
RIGHT JOIN Public.My_Merge_Target AS t
ON s.Id = t.Id
WHERE s.Id IS NULL
) AS src
WHERE trg.Id = src.Id;
/*
-----------------------
number of rows deleted
-----------------------
1
-----------------------
*/
SELECT * FROM Public.My_Merge_Target ORDER BY Id;
/*
------------------------------------
Id | Name
------------------------------------
1 | Stay as is
2 | This is the new name
*/
MERGE
INTO Public.My_Merge_Target AS trg
USING (
SELECT Id, Name
FROM Public.My_Merge_Source
) AS src
ON
trg.Id = src.Id
WHEN
MATCHED
AND (src.Name != trg.Name) THEN UPDATE
SET Name = src.Name
WHEN
NOT MATCHED THEN INSERT (Id, Name)
VALUES (src.Id, src.Name)
;
/*
-------------------------------------------------
number of rows inserted | number of rows updated
-------------------------------------------------
1 | 1
-------------------------------------------------
*/
SELECT * FROM Public.My_Merge_Target ORDER BY Id;
/*
------------------------------------
Id | Name
------------------------------------
1 | Stay as is
2 | This is the new name for id=2
4 | A new row
*/

Multiple entries in to table variable using Output INTO clause

My use case is as below :
Create table Emp
(
id int ,
fname varchar(100),
lname varchar(100)
)
declare #aud table (prop varchar(1000),[oldval] varchar(100),[toval] varchar(100))
INSERT INTO EMP(id, fname, lname)
OUTPUT 'fname', '', inserted.fname INTO #aud (prop, [oldval], [toval])
VALUES (1,'malcolm','dsouza')
SELECT * FROM #aud
I want the data to be formed like:
prop oldval toval
------------------------
fname malcolm
lname dsouza
I know we cannot have have multiple inserts using OUTPUT INTO clause, but is there any other way I can form a data for inserted rows as column based?
You need to try below steps:
OUTPUT clause simply inserts the rows in to the table variable i.e., one row to one row mapping. You need to modify your table definition accordingly.
Now, You can use CROSS APPLY to get the result you want.
**TEST SETUP**
Create table #Emp(
id int ,
fname varchar(100),
lname varchar(100)
)
TEST
declare #aud table(oldfname varchar(100),fname varchar(100),oldlname varchar(100), lname varchar(100));
INSERT INTO #EMP(id,fname,lname)
OUTPUT '' as oldfname,inserted.fname, '' as oldlname, inserted.lname INTO #aud
values (1,'malcolm','dsouza');
SELECT property, oldval, newval as oldValue
FROM #aud
CROSS APPLY
( values('fname',oldfname,fname),('lname',oldlname, lname)) as t(property,oldVal, newVal)
Result Set
+----------+--------+----------+
| property | oldval | oldValue |
+----------+--------+----------+
| fname | | malcolm |
| lname | | dsouza |
+----------+--------+----------+

How to get desired result in SQL Server

In my application there is a table to store text and another table to store it's respective images..
My table structure goes as follows (tbl_article):
article_id | Page_ID | article_Content
-----------+---------+-----------------
1 | 1 | hello world
2 | 1 | hello world 2
where article_id is the pk and auto incremented.
Now in my other table (tbl_img):
image_id| image_location|article_id | page_id
--------+---------------+-----------+---------
1 | imgae locat | 1 | 1
2 | image loc2 | 2 | 1
where image_id is the pk and auto incremented.
In both table I am inserting data through table valued parameter, and in second table article_id is referencing article_id of the first table.
To get auto incremented column value I am using output clause:
DECLARE #TableOfIdentities TABLE
(
IdentValue INT,
PageId INT
)
INSERT INTO tbl_article(page_id, article_content)
OUTPUT Inserted.article_id, #pageId INTO #TableOfIdentities (IdentValue, PageId)
SELECT page_id, slogan_body_header
FROM #dtPageSlogan
INSERT INTO tbl_img(page_id, image_location)
SELECT page_id, image_location
FROM #dtPageImageContent
But now I have to insert values from #TableOfIdentities into article_id of tbl_img - how to do that?
You need an additional column , a temporary article id generated from your code to link images and related articles properly. So you can use MERGE with OUTPUT, because with merge you can refer to columns from both the target and the source and build your TableOfIdentities tvp properly, then join it with dtPageImageContent to insert on tbl_img.
CREATE TABLE tbl_article (
article_id INT IDENTITY(1, 1) PRIMARY KEY
, Page_ID INT
, article_Content NVARCHAR(MAX)
);
CREATE TABLE tbl_img (
image_id INT IDENTITY(1, 1) PRIMARY KEY
, image_location VARCHAR(256)
, article_id INT
, Page_ID INT
);
DECLARE #TableOfIdentities TABLE
(
IdentValue INT,
PageId INT,
tmp_article_id INT
);
DECLARE #dtPageSlogan TABLE(
tmp_article_id INT -- generated in your code
, page_id INT
, slogan_body_header NVARCHAR(MAX)
);
DECLARE #dtPageImageContent TABLE (
page_id INT
, image_location VARCHAR(256)
, tmp_article_id INT -- needed to link each image to its article
)
-- create sample data
INSERT INTO #dtPageSlogan(tmp_article_id, page_id, slogan_body_header)
VALUES (10, 1, 'hello world');
INSERT INTO #dtPageSlogan(tmp_article_id, page_id, slogan_body_header)
VALUES (20, 1, 'hello world 2');
INSERT INTO #dtPageImageContent(page_id, image_location, tmp_article_id)
VALUES (1, 'image loc1', 10);
INSERT INTO #dtPageImageContent(page_id, image_location, tmp_article_id)
VALUES (1, 'image loc2', 20);
-- use merge to insert tbl_article and populate #TableOfIdentities
MERGE INTO tbl_article
USING (
SELECT ps.page_id, ps.slogan_body_header, ps.tmp_article_id
FROM #dtPageSlogan as ps
) AS D
ON 1 = 2
WHEN NOT MATCHED THEN
INSERT(page_id, article_content) VALUES (page_id, slogan_body_header)
OUTPUT Inserted.article_id, Inserted.page_id, D.tmp_article_id
INTO #TableOfIdentities (IdentValue, PageId, tmp_article_id)
;
-- join using page_id and tmp_article_id fields
INSERT INTO tbl_img(page_id, image_location, article_id)
-- select the "IdentValue" from your table of identities
SELECT pic.page_id, pic.image_location, toi.IdentValue
FROM #dtPageImageContent pic
-- join the "table of identities" on the common "page_id" column
INNER JOIN #TableOfIdentities toi
ON pic.page_Id = toi.PageId AND pic.tmp_article_id = toi.tmp_article_id
;
You can try it on fiddle
You need to join the #dtPageImageContent table variable with the #TableOfIdentities table variable on their common page_id to get those values:
-- add the third column "article_id" to your list of insert columns
INSERT INTO tbl_img(page_id, image_location, article_id)
-- select the "IdentValue" from your table of identities
SELECT pic.page_id, pic.image_location, toi.IdentValue
FROM #dtPageImageContent pic
-- join the "table of identities" on the common "page_id" column
INNER JOIN #TableOfIdentities toi ON pic.page_Id = toi.page_id

Bulk Replace in SQL-Server

Is it possible to do bulk replace without while loop or what is the best way
Table-1
+-------+--------+
| name | value |
+-------+--------+
| #1# | one |
| #2# | two |
| #3# | three |
+-------+--------+
Table-2 (updated: there is more than one different tokens in table2)
+-----------------------+
| col1 |
+-----------------------+
| string #1# string #2# |
| string #2# string #1# |
| string #3# string #2# |
+-----------------------+
I like to replace all token from Table-2 with Table-1's value column respectively.
Expected Result
+-------------------------+
| col1 |
+-------------------------+
| string one string two |
| string two string one |
| string three string two |
+-------------------------+
Current solution with While loop
declare #table1 table(name nvarchar(50),value nvarchar(50))
insert into #table1 values('#1#','one'),('#2#','two'),('#1#','three')
declare #table2 table(col1 nvarchar(50))
insert into #table2 values('string #1# string #2#'),('string #2# string #1#'),('string #3# string #2#')
WHILE EXISTS (SELECT 1 FROM #table2 t2 INNER JOIN #table1 t1 ON CHARINDEX(t1.name,[col1])>0)
BEGIN
UPDATE #table2
SET col1=REPLACE(col1,name,value)
FROM #table1
WHERE CHARINDEX(name,[col1])>0
END
select * from #table2
Thanks
I suppose you use Sql Server (you've tagged with tsql):
I've run this query on Sql fiddle with 2012 version, but on my PC I've tried with 2008r2 version.
You can procede in this way:
UPDATE table2
SET col1 = REPLACE(col1,
(SELECT name FROM table1 WHERE col1 LIKE '%' + table1.NAME + '%'),
(SELECT value FROM table1 WHERE col1 LIKE '%' + table1.NAME + '%'))
Sql Fiddle
If you want show only the value without UPDATE you can proceed in this way:
SELECT REPLACE(T2.col1, T1.name, T1.value)
FROM table1 T1
JOIN table2 T2
ON T2.col LIKE '%' T1.name + '%'
EDIT
After editing of question / comment my answer is not complete because on the same row can exist more one token. I'm thinking... :)
I thought: :D
IMHO: You must create a loop on your table because the presence of several tokens don't resolve with a single UPDATE statement with subquery, because as you written, the subquery return more than one value.
In Sql Server the REPLACE function change only token, so if you want change in one step two token you must nest your REPLACE function, but we have a number undefined of token in a row so we can't prevent to apply the exact number of REPLACE. An help you can have using a cursor and a dynamic SQL query build on runtime, so you can do a single UPDATE per row. If you want a guide line to use CURSOR and DYNAMIC SQL, please write me. Good night ;)
You can do bulk replacement with this simple piece of code:
update #table2
set col1= left(a.col1,6)+' ' + b.value from #table2 a
join #table1 b on b.name=substring(a.col1,8,3)
select * from #table2
Basically, it updates the column with a new field value.

A trigger to update one column value to equal the pkid of the record

I need to write a trigger that will set the value in column 2 = to the value in column 1 after a record has been created.
This is what I have so far:
create trigger update_docindex2_to_docid
ON dbo.TABLENAME
after insert
AS BEGIN
set DOCINDEX2 = DOCID
END;
I answered my own question one I sat and thought about it long enough....
This seems way to simple. I'm concerned that I'm going break something because I don't have a where condition that would identify the correct record. I want this to update docindex2 to the newly created DOCID after a record is created in the database. The docid is the pkid.
Any ideas/suggestions are appreciated....
Are you looking for something like this?
CREATE TABLE Table1 (docid INT IDENTITY PRIMARY KEY, docindex2 INT);
CREATE TRIGGER tg_mytrigger
ON Table1 AFTER INSERT
AS
UPDATE t
SET t.docindex2 = t.docid
FROM Table1 t JOIN INSERTED i
ON t.docid = i.docid;
INSERT INTO Table1 (docindex2) VALUES(0), (0);
Contents of Table after insert
| DOCID | DOCINDEX2 |
---------------------
| 1 | 1 |
| 2 | 2 |
Here is SQLFiddle demo

Resources