Write a MERGE Statement for mis-matching values - sql

I have a table, tblstars_new, which is updated weekly from the client. I have another table, tblstars, which needs to import new and updated data from tblstars_new.
Finding rows in tblstars_new that do not exist in tblstars, and then adding then to tblstars is simple.
But, I also need to find rows in tblstars_new in which the column PandA_Code has changed, and then update the identical row in tblstars.
This query tells me which rows from tblstars_new have PandA_Code's that have changed and need to be updated in tblstars.
SELECT
sn.*
FROM
tblstars_new sn
JOIN tblstars s ON sn.Student_ID_Number = s.Student_ID_Number AND sn.PandA_Code != s.PandA_Code
I'm trying to figure out a MERGE statement that will make the changes. As I'm doing in Prod, I can't really play around. Two questions:
1) Is it possible to see the changes without actually doing them?
2) Is the MERGE statement below correct?
BEGIN TRAN;
MERGE tblstars AS T -- Target
USING tblstars_new AS S -- Source
ON
(T.Student_ID_Number = S.Student_ID_Number AND T.PandA_Code != S.PandA_Code)
WHEN NOT MATCHED BY TARGET
THEN
UPDATE SET T.PandA_Code = S.PandA_Code
OUTPUT $action;
ROLLBACK TRAN;
GO

Here is the MERGE query that satisfy your requests.
BEGIN TRAN;
MERGE tblstars AS T -- Target
USING tblstars_new AS S -- Source
ON T.Student_ID_Number = S.Student_ID_Number -- They shoudl match by PK
WHEN MATCHED AND AND T.PandA_Code != S.PandA_Code
THEN -- when matched and PandA_Code different update them
UPDATE SET T.PandA_Code = S.PandA_Code
WHEN NOT MATCHED BY TARGET -- When not matched by TARGET (there is in source but not in target)
THEN INSERT (<field1, field2, ...>)
VALUES (<S.field1, S.filed2, ...>) -- then insert them
OUTPUT deleted.*, $action, inserted.* INTO #TheTempTable;
SELECT * FROM #TheTempTable; -- here you can see the cnages and rollback if something is wrong
ROLLBACK TRAN;
GO

This is simply done by using join instead of merge statement:
UPDATE T SET T.PandA_Code = S.PandA_Code FROM tblstart T
INNER JOIN tblstars_new P ON P.Student_ID_Number = T.Student_ID_Number
WHERE T.PandA_Code <> S.PandA_Code

Related

How do you merge two different SQL Server 2012 database tables in single stored procedure?

MERGE [160.80.3.220].[sample].[dbo].[Products] AS TARGET
USING UpdatedProducts AS SOURCE ON (TARGET.ProductID = SOURCE.ProductID)
-- When records are matched, update
-- the records if there is any change
WHEN MATCHED AND TARGET.ProductName <> SOURCE.ProductName
OR TARGET.Rate <> SOURCE.Rate THEN
UPDATE
SET TARGET.ProductName = SOURCE.ProductName,
TARGET.Rate = SOURCE.Rate
-- When no records are matched, insert
-- the incoming records from source
-- table to target table
WHEN NOT MATCHED BY TARGET THEN
INSERT (ProductID, ProductName, Rate)
VALUES (SOURCE.ProductID, SOURCE.ProductName, SOURCE.Rate)
-- When there is a row that exists in target table and
-- same record does not exist in source table
-- then delete this record from target table
WHEN NOT MATCHED BY SOURCE THEN
DELETE
-- $action specifies a column of type nvarchar(10)
-- in the OUTPUT clause that returns one of three
-- values for each row: 'INSERT', 'UPDATE', or 'DELETE',
-- according to the action that was performed on that row
OUTPUT $action,
DELETED.ProductID AS TargetProductID,
DELETED.ProductName AS TargetProductName,
DELETED.Rate AS TargetRate,
INSERTED.ProductID AS SourceProductID,
INSERTED.ProductName AS SourceProductName,
INSERTED.Rate AS SourceRate;
SELECT ##ROWCOUNT;
GO
Since:
target_table cannot be a remote table. target_table cannot have any
rules defined on it.
What you could do is first insert all the data from your linked server to your current server database table using four-part query, then do Merge.
OR:
using source table as remote table because remote table is supported in USING. So what you could do alternatively is:
first Change connection to [160.80.3.220].[sample]
then:
MERGE [dbo].[Products] AS TARGET
USING [linked server instance].[database].[schema].UpdatedProducts AS SOURCE

MERGE statement DELETE alternative in SQL Server

I have a Query using T-SQL MERGE Statement. Due to performance issues I am re writing the Query using IF Exists Update and If Not Exists Insert. I am able to write Insert/Update without any issue. But I am unable to handle the DELETE. Can some one please help me on this?
Here is the sample
---SAMPLE MERGE STATEMENT
MERGE
member_topic AS target
USING
someOtherTable AS source
ON
target.mt_member = source.mt_member
WHEN MATCHED THEN
UPDATE SET target.mt_notes = source.mt_notes
WHEN NOT MATCHED THEN
INSERT (mt_member, mt_topic, mt_notes) VALUES (source.mt_member, source.mt_notes)
WHEN NOT MATCHED BY SOURCE THEN
DELETE member_topic;
--UPDATE
UPDATE T SET T.mt_notes = S.mt_notes
FROM member_topic T
JOIN someOtherTable S ON T.mt_member=S.mt_member
--INSERT
INSERT INTO member_topic(mt_member, mt_topic, mt_notes)
SELECT mt_member, mt_topic, mt_notes
FROM someOtherTable S
WHERE NOT EXISTS(SELECT 1
FROM member_topic T
WHERE T.mt_member=S.mt_member)
How to handle
WHEN NOT MATCHED BY SOURCE THEN
DELETE member_topic;
in single DELETE Statement.
a sample script to be embedded between begin and end in proc
MERGE dbo.Tablet AS TARGET
USING dbo.QueryView AS SOURCE
ON (
TARGET.[ID] = SOURCE.[ID]
)
WHEN MATCHED
THEN
UPDATE SET
TARGET.[ID] = SOURCE.[ID]
WHEN NOT MATCHED BY TARGET THEN
INSERT (ID, [Name] )
VALUES (SOURCE.[ID], SOURCE.[Name] )
WHEN NOT MATCHED BY SOURCE THEN
DELETE;
Try
DELETE T
FROM member_topic T
WHERE NOT EXISTS(SELECT 1
FROM someOtherTable S
WHERE T.mt_member=S.mt_member)
DELETE t
FROM member_topic t
LEFT JOIN someOtherTable s ON t.mt_member = s.mt_member
WHERE s.mt_member IS NULL

Performance issues with UPDATE in AFTER UPDATE Trigger

I have a small performance issue with one of my database triggers in my MS-SQL Server 2014 database.
CREATE TRIGGER [dbo].[TRG_T_TPM_Vehicle_Update] ON [dbo].[T_TPM_Vehicle]
AFTER UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
UPDATE T_TPM_Vehicle SET LastUpdated = getdate()
WHERE Vehicle_Number IN (SELECT Vehicle_Number FROM inserted)
UPDATE T_TPM_Vehicle SET [DisturbedSince] = getdate()
WHERE Vehicle_Number IN (SELECT Vehicle_Number FROM inserted WHERE inserted.Emergency_Stop = 1)
AND Vehicle_Number IN (SELECT Vehicle_Number FROM deleted WHERE deleted.Emergency_Stop = 0)
INSERT INTO T_TPM_Vehicle_HistoricalData
([Vehicle_Ref]
,[Vehicle_Number]
,[Vehicle_Type]
,[Pos_X]
,[Pos_Y]
,[Alpha]
,[LastAutoPos_X]
,[LastAutoPos_Y]
,[LastAutoAlpha]
,[Automatic]
,[Manual]
,[Blocked]
,[Loaded]
,[Stoped]
,[Emergency_Stop]
,[User_Required]
,[BatteryAlmostEmpty]
,[BatteryEmpty]
,[BatteryLevel]
,[ChargingRelaisEnable]
,[NavOK]
,[PowerOn]
,[Available]
,[OperatingMinutes]
,[UpdateOperatingMinutes]
,[DataChangedByVIS]
,[Blockingsreleased]
,[Cancelled]
,[ProductID]
,[HUIdent1]
,[HUIdent2]
,[HUType]
,[DisturbedSince])
SELECT inserted.[Vehicle_Ref]
,inserted.[Vehicle_Number]
,inserted.[Vehicle_Type]
,inserted.[Pos_X]
,inserted.[Pos_Y]
,inserted.[Alpha]
,inserted.[LastAutoPos_X]
,inserted.[LastAutoPos_Y]
,inserted.[LastAutoAlpha]
,inserted.[Automatic]
,inserted.[Manual]
,inserted.[Blocked]
,inserted.[Loaded]
,inserted.[Stoped]
,inserted.[Emergency_Stop]
,inserted.[User_Required]
,inserted.[BatteryAlmostEmpty]
,inserted.[BatteryEmpty]
,inserted.[BatteryLevel]
,inserted.[ChargingRelaisEnable]
,inserted.[NavOK]
,inserted.[PowerOn]
,inserted.[Available]
,inserted.[OperatingMinutes]
,inserted.[UpdateOperatingMinutes]
,inserted.[DataChangedByVIS]
,inserted.[Blockingsreleased]
,inserted.[Cancelled]
,inserted.[ProductID]
,inserted.[HUIdent1]
,inserted.[HUIdent2]
,inserted.[HUType]
,inserted.[DisturbedSince]
FROM inserted
END
What it basically does is it sets the LastUpdated column for all rows in inserted and the DisturbedSince column for a subset of the inserted rows.
Finally the inserted rows get copied to a history table. (Every change on any row must be saved for two days). Older data gets deleted by a maintenance job.
As we have up to ~ 300 rows updated per second (Updates to rows can be batched together) We create a big amount of data and recursive updates.
I've now found the INSTEAD OF UPDATE triggers which seem to solve the recursive UPDATE problem caused by my trigger but I would have to process every row of the inserted table one by one with an update statement in the trigger.
I'm not sure if this is really faster. Does anyone of you have a recommendation?
What I really need is to tweak / extend the data rows before they are send to the table. Is there an approach for this?
e.g.: Something like:
CREATE TRIGGER ... INSTEAD OF UPDATE
AS
BEGIN
UPDATE inserted SET LastUpdated = getdate()
UPDATE inserted SET DisturbedSince
WHERE Vehicle_Number IN (SELECT Vehicle_Number FROM inserted WHERE inserted.Emergency_Stop = 1)
AND Vehicle_Number IN (SELECT Vehicle_Number FROM deleted WHERE deleted.Emergency_Stop = 0)
"SAVE INSERTED"
END
and an AFTER UPDATE TRIGGER with the storage of the changed data to the history table.
Thank you for any suggestions.
Thomas
You're right to think that using an INSTEAD OF trigger is the right way to go rather than an AFTER trigger, when you're wanting to change data within the same table as well.
It would be something like:
CREATE TRIGGER [dbo].[TRG_T_TPM_Vehicle_Update] ON [dbo].[T_TPM_Vehicle]
INSTEAD OF UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
UPDATE tgt
SET
Vehicle_Ref = i.Vehicle_Ref,
Vehicle_Type = i.Vehicle_Type,
...
LastUpdated = getdate(),
DisturbedSince = CASE WHEN i.Emergency_Stop=1 and d.Emergency_Stop=0
THEN getdate() ELSE d.DisturbedSince END
OUTPUT
inserted.[Vehicle_Ref]
,inserted.[Vehicle_Number]
,inserted.[Vehicle_Type]
...
,inserted.[HUIdent2]
,inserted.[HUType]
,inserted.[DisturbedSince]
INTO T_TPM_Vehicle_HistoricalData
([Vehicle_Ref]
,[Vehicle_Number]
,[Vehicle_Type]
...
,[HUIdent2]
,[HUType]
,[DisturbedSince])
FROM
T_TPM_Vehcile tgt
inner join
inserted i
on
tgt.Vehicle_Number = i.Vehicle_Number
inner join
deleted d
on
tgt.Vehicle_Number = d.Vehicle_Number
You'll note that I've combined both the UPDATEs and the INSERT into the history table into a single compound statement.
You'll also note that it's slightly confusing because there are two inserteds in play here - the inserted as part of the trigger (aliased as i to sidestep some of the confusion) and the inserted as part of the OUTPUT clause.

SQL Server : use a transaction in the then clause of merge

In a merge statement, when not matched, I need to insert into target table and update another table. How can I achieve this?
merge table1 as TARGET
using table2 as SOURCE ON <conditions>
when not matched by target
then
/*--------can i do this?---------*/
BEGIN TRANSACTION
insert (columnnames) values(v1,..., vn) /*insert into target*/
update source
set column1 = value1 /*update source*/
END TRANSACTION;
The whole merge statement is atomic (either the whole statement completes or the whole statement gets rolled back) so that you don't need to create a transaction.
Here's an example from Microsoft:
MERGE Production.UnitMeasure AS target
USING (SELECT #UnitMeasureCode, #Name) AS source (UnitMeasureCode, Name)
ON (target.UnitMeasureCode = source.UnitMeasureCode)
WHEN MATCHED THEN
UPDATE SET Name = source.Name
WHEN NOT MATCHED THEN
INSERT (UnitMeasureCode, Name)
VALUES (source.UnitMeasureCode, source.Name)
OUTPUT deleted.*, $action, inserted.* INTO #MyTempTable;

Having MERGE INTO do 2 things when Not Matched?

I am using the MERGE INTO statement to work with some tables, and I've a simple question. How can I make a Matched/Not Matched Statement do 2 things? For example, I have this and it works:
MERGE INTO VMFG.dbo._AR_Mill1ActiveSchedule t --target
USING #temp --source
On t.Base_ID = #temp.WORKORDER_BASE_ID AND t.Lot_ID = #temp.WORKORDER_Lot_ID
WHEN MATCHED AND #temp.rollstatus = 'R' THEN
UPDATE
SET t.sawStatus = #temp.sawstatus, t.rollStatus = #temp.rollstatus
WHEN NOT MATCHED BY TARGET THEN
INSERT (BASE_ID, LOT_ID, sawStatus, rollstatus, preheatcheck) VALUES (#temp.WorkOrder_Base_ID, #temp.WorkOrder_Lot_ID, #temp.sawStatus, #temp.rollStatus, 'False')
WHEN NOT MATCHED BY SOURCE AND t.SawStatus = 'C' THEN
Delete ;
Drop Table #temp
Now, I want to accomplish something like this:
MERGE INTO VMFG.dbo._AR_Mill1ActiveSchedule t --target
USING #temp --source
On t.Base_ID = #temp.WORKORDER_BASE_ID AND t.Lot_ID = #temp.WORKORDER_Lot_ID
WHEN MATCHED AND #temp.rollstatus = 'R' THEN
UPDATE
SET t.sawStatus = #temp.sawstatus, t.rollStatus = #temp.rollstatus
WHEN NOT MATCHED BY TARGET THEN
INSERT (BASE_ID, LOT_ID, sawStatus, rollstatus, preheatcheck) VALUES (#temp.WorkOrder_Base_ID, #temp.WorkOrder_Lot_ID, #temp.sawStatus, #temp.rollStatus, 'False')
WHEN NOT MATCHED BY SOURCE AND t.SawStatus = 'C' THEN
INSERT INTO _AR_Mill1RemovedWO (BASE_ID, LOT_ID) VALUES (#temp.WorkOrder_Base_ID, #temp.WorkOrder_Lot_ID)
Delete ;
Drop Table #temp
However, when I run this I get the error "Incorrect syntax near the keyword 'INTO'".
I've tried using the OUTPUT clause but I couldn't get it to work correctly, and would rather use an insert statement anyways if possible.
Thank you :)
EDIT:
The Output clause would be awesome to use, but I'll explain why it hasn't worked. When I used it, i.e.:
....
WHEN NOT MATCHED BY SOURCE AND t.SawStatus = 'C' THEN
--INSERT INTO _AR_Mill1RemovedWO (BASE_ID, LOT_ID) VALUES (#temp.WorkOrder_Base_ID, #temp.WorkOrder_Lot_ID)
Delete
OUTPUT deleted.Base_ID, deleted.Lot_ID INTO VMFG.dbo._AR_Mill1RemovedWO
;
Drop Table #temp
I run in to a problem. It adds rows to my table that weren't actually deleted. It's like it's just adding everything, regardless of being deleted. Is there a reason for this?
This is impossible to do with a single MERGE statement. Use the OUTPUT clause to direct information about the writes executed into a table variable. Execute a second statement such as INSERT to write to the second table.