For a time scheduling project, I have two tables: tbl_timeslots which holds available times with slotid as the primary key and totalmembers which counts the number of appointments made for this slot, and tbl_appointments with primary key apptid which holds the actual appointments, with slotid as a foreign key linking to the slot information.
I need to automatically update the totalmembers column any time an appointment is created/deleted/changed. The trigger I wrote (shown below) does not update the correct number of appointments in the tbl_timeslots column totalmembers.
CREATE TABLE tbl_timeslots
(
slotid int ,
fromdate datetime ,
todate datetime ,
totalmembers int
)
INSERT tbl_timeslots (slotid, fromdate, todate, totalmembers)
VALUES (1, '2016-01-01 10:00:00', '2016-01-01 11:00:00', 0)
INSERT tbl_timeslots (slotid, fromdate, todate, totalmembers)
VALUES (2, '2016-01-01 11:00:00', '2016-01-01 12:00:00', 0)
CREATE TABLE tbl_appointments
(
apptid int ,
slotid int ,
firstname varchar(10) ,
lastname varchar(10)
)
INSERT tbl_appointments (apptid, slotid, firstname, lastname)
VALUES (1, 1, 'Mark', 'Twain')
INSERT tbl_appointments (apptid, slotid, firstname, lastname)
VALUES (2, 1, 'Thomas', 'Jefferson')
INSERT tbl_appointments (apptid, slotid, firstname, lastname)
VALUES (3, 2, 'Donald', 'Duck')
CREATE TRIGGER [dbo].[tr_totalmembers]
ON [dbo].[TBL_appointments]
AFTER UPDATE, INSERT, DELETE
AS
BEGIN
UPDATE tbl_timeslots
SET totalmembers = (SELECT COUNT(1)
FROM tbl_appointments a
WHERE tbl_timeslots.slotid = a.slotid)
FROM inserted i
INNER JOIN deleted d ON i.apptid = d.apptid
WHERE
d.slotid <> i.slotid
AND (tbl_timeslots.slotid = i.slotid OR tbl_timeslots.slotid = d.slotid)
END
I just modified the trigger a bit and it worked for me :
CREATE TRIGGER [dbo].[tr_totalmembers]
ON [dbo].[TBL_appointments]
AFTER UPDATE, INSERT, DELETE
AS
BEGIN
UPDATE tbl_timeslots
SET totalmembers = a.cnt
from
(
SELECT slotid,COUNT(1) as cnt
FROM tbl_appointments
group by slotid
) a
WHERE
tbl_timeslots.slotid=a.slotid
END
Or in case if there is a specific problem please mention so that we could look into it .
A better way to compute totalmembers is to create and use an indexed view:
CREATE VIEW dbo.vw_timeslots_with_totalmembers
WITH SCHEMA_BINDING
AS
SELECT a.slotid, COUNT_BIG(*) AS totalmembers
FROM dbo.tbl_appointments a
GROUP BY a.slotid
GO
CREATE UNIQUE CLUSTERED INDEX IUC_vw_timeslots_with_totalmembers_slotid
ON dbo.vw_timeslots_with_totalmembers (slotid)
GO
DECLARE #slotid INT = 123
SELECT totalmembers
FROM dbo.vw_timeslots_with_totalmembers WITH(NOEXPAND) -- This table hint is needed in order to force usage of indexed view
WHERE slotid = #slotid
GO
Note: please read following notes regarding the proper configuration of SETtings (see section Required SET Options for Indexed Views): https://msdn.microsoft.com/en-us/library/ms191432.aspx .
Note #2: if last SELECT statement returns 0 rows this means that current slot doesn't have appointments (I assume that current slot is valid).
Related
It's my first time working with T-SQL Merge and I have been trying to write a Stored Procedure using with the following condition:
If record is present then insert into Changes table the Id, old value (before update) and the new value and after that update the old value from Affected
if record is not present then insert into Changes the Id, empty for old and the new value and after that insert new record in Affected
if record didn't match then insert into Changes the Id, old value (value before deletion) and empty as new value and after that delete the record
Here are the tables I used
CREATE TABLE Affected
(
[Id] int IDENTITY(1,1) NOT NULL,
[ZoneId] int NOT NULL,
[Name] varchar(100) NOT NULL,
[Value] varchar(100)
)
CREATE TABLE Changes
(
[AffectedId] int NOT NULL,
[OldValue] varchar(100),
[NewValue] varchar(100)
)
And here is my stored procedure which takes two zone ids as input parameters
CREATE PROCEDURE spAffectChanges
#ZoneId1 int,
#ZoneId2 int
AS
IF(#ZoneId1 < 10)
BEGIN
;WITH fromQ AS
(
SELECT DISTINCT Name, Value
FROM Affected
WHERE ZoneId = #ZoneId1 AND Name NOT IN ('aaa', 'bbb', 'ccc')
), toQ AS
(
SELECT DISTINCT Name, Value
FROM Affected
WHERE ZoneId = #ZoneId2 AND Name NOT IN ('aaa', 'bbb', 'ccc')
)
MERGE toQ
USING fromQ ON (toQ.Name = fromQ.Name)
WHEN MATCHED AND fromQ.Value<>toQ.Value THEN
----first insert
INSERT INTO Changes (AffectedId, OldValue, NewValue)
VALUES(toQ.Id, toQ.Value, fromQ.Value)
----then update
UPDATE SET toQ.Value=fromQ.Value
WHEN NOT MATCHED BY toQ THEN
----first insert
INSERT INTO Changes (AffectedId, OldValue, NewValue)
VALUES(toQ.Id, '', fromQ.Value)
----second insert
INSERT (Name, Zone, Value)
VALUES (fromQ.Name, #ZoneId1, fromQ.Value)
WHEN NOT MATCHED BY fromQ THEN
----first insert
INSERT INTO Changes (AffectedId, OldValue, NewValue)
VALUES(toQ.Id, toQ.Value, '')
----then delete
DELETE
END
Is there a way to perform both insert to Changes and Update/Insert/Delete for Affected?
Thanks in advance
You can use the OUTPUT clause to do this.
Notes:
MERGE requires a semi-colon terminator. ;WITH is silly, ; is not a beginning-ator, it is meant to be placed at the end of every command
The options for WHEN NOT MATCHED are BY SOURCE and BY TARGET (this is the default)
I don't quite understand how you have a DISTINCT on the target, I don't believe it is allowed, nor how it makes sense to do so.
WITH fromQ AS
(
SELECT DISTINCT Name, Value
FROM Affected
WHERE ZoneId = #ZoneId1 AND Name NOT IN ('aaa', 'bbb', 'ccc')
), toQ AS
(
SELECT Name, Value
FROM Affected
WHERE ZoneId = #ZoneId2 AND Name NOT IN ('aaa', 'bbb', 'ccc')
)
MERGE toQ
USING fromQ ON (toQ.Name = fromQ.Name)
WHEN MATCHED AND fromQ.Value <> toQ.Value THEN
UPDATE
SET toQ.Value = fromQ.Value
WHEN NOT MATCHED BY TARGET THEN
INSERT (Name, Zone, Value)
VALUES (fromQ.Name, #ZoneId1, fromQ.Value)
WHEN NOT MATCHED BY SOURCE THEN
DELETE
OUTPUT inserted.Id, ISNULL(deleted.Value, ''), ISNULL(inserted.Value, '')
INTO Changes (AffectedId, OldValue, NewValue)
;
For the following example I set shipping method to 'UPS'
CREATE TABLE [dbo].[Customer] (CustomerID int primary key, ShipMethodRef INT)
INSERT INTO [dbo].[Customer] VALUES (5497, 20);
CREATE TABLE [dbo].ShipMethod(ShipMethodID int PRIMARY KEY, Name varchar(10));
INSERT INTO [dbo].ShipMethod VALUES (20, 'Fedex'), (21, 'UPS')
UPDATE [dbo].[Customer]
set ShipMethodRef = CASE WHEN EXISTS (SELECT ShipMethodID from [dbo].[ShipMethod]
WHERE [dbo].[ShipMethod].Name = 'UPS')
THEN (SELECT ShipMethodID from [dbo].[ShipMethod]
WHERE [dbo].[ShipMethod].Name = 'UPS')
ELSE curTable.ShipMethodRef END
OUTPUT ShipMethod.Name as ShipMethodName
FROM [dbo].[Customer] curTable
JOIN [dbo].ShipMethod ShipMethod ON curTable.ShipMethodRef = ShipMethod.ShipMethodID
WHERE CustomerID=5497;
The OUTPUT clause returns Fedex - How can I change it to reflect the post insert state that the customer's shipping method is 'UPS' (as their shipping method Id is now 21)?
I don't think this can be done with a single statement except in the way Martin showed in his comment, but you can get the output from inserted into a table variable or a temporary table and then select from that joined to the translation tables.
Here's how I would do that (note the update statement is simplified):
DECLARE #UpdatedIds AS TABLE (ShipMethodID int);
UPDATE [dbo].[Customer]
SET ShipMethodRef = COALESCE((
SELECT ShipMethodID
FROM [dbo].[ShipMethod]
WHERE [dbo].[ShipMethod].Name = 'UPS'
), ShipMethodRef)
OUTPUT inserted.ShipMethodRef INTO #UpdatedIds
FROM [dbo].[Customer]
WHERE CustomerID=5497;
SELECT SM.ShipMethodID, SM.Name
FROM [dbo].ShipMethod AS SM
JOIN #UpdatedIds AS Updated
ON SM.ShipMethodID = Updated.ShipMethodID
I have been trying to get acclimated to set based processing with SQL Server. Below is a simplified version of cursor processing for this task. It involves creating an order from items in a shopping cart. The order is created, line items are added to the order details table, the total is accumulated and eventually updated on the order table. Can anyone suggest how to do this with a set based approach instead of a cursor?
One other question is that in most cases the cursor will process at most 10 or 12 line items at a time. Is that enough of a reason to not have to consider the set based approach?
declare getCart2 cursor for
select MemberID,ProductID,Quantity,Price
from Carts
where MemberID = #MemberID
open getCart2
fetch next from getCart2 into #MemberID,#ProductID,#Quantity,#Price
Insert into Orders
(MemberID,TotalAmount0
Values
(#MemberID, 0.00)
set #OrderID = ##Identity
while ##FETCH_STATUS = 0 Begin
Insert into OrderDetails
(OrderID,ProductID,Quantity)
Values
(#OderID,#ProductID,#Quantity)
set #TotalAmout = #TotalAmount + (#Quantity * #Price)
set #PrevMemberID = #MemberID
fetch next from getCart2 into #MemberID,#ProductID,#Quantity,#Price
End
close getCart2
deallocate getCart2
Update Orders
Set TotalAmount = #TotalAmount
Where OrderID = #OrderID
Thanks for your help.
Here goes an approach:
In this case I am creating a temporary table variable that will store the order id's on it.
Then, it performs the insertions on the Order table and after that, in the OrderDetails.
Finally, it computes the TotalAmount and updates on the Orders table.
Although you don't have it in your code (and in mine as well), but I recommend you to use this code inside a transaction.
Hope it helps you improve your performance.
USE [tempdb];
GO
SET NOCOUNT ON;
IF OBJECT_ID(N'dbo.Carts', N'U') IS NOT NULL DROP TABLE [dbo].[Carts];
IF OBJECT_ID(N'dbo.Orders', N'U') IS NOT NULL DROP TABLE [dbo].[Orders];
IF OBJECT_ID(N'dbo.OrderDetails', N'U') IS NOT NULL DROP TABLE [dbo].[OrderDetails];
GO
-- Creates the tables like you have
CREATE TABLE [dbo].[Carts] (MemberID INT, ProductID INT, Quantity INT, Price DECIMAL(10, 2));
CREATE TABLE [dbo].[Orders] (OrderID INT IDENTITY(1, 1), MemberID INT, TotalAmount DECIMAL(10, 2));
CREATE TABLE [dbo].[OrderDetails] (OrderID INT, ProductID INT, Quantity INT);
-- Inserts dummy data
INSERT INTO [dbo].[Carts] VALUES (1001, 80, 5, 25.00);
INSERT INTO [dbo].[Carts] VALUES (1002, 120, 2, 12.90);
INSERT INTO [dbo].[Carts] VALUES (1010, 70, 3, 12.00)
INSERT INTO [dbo].[Carts] VALUES (1034, 176, 5, 45.00);
-- Temporary table that stores the inserted Order ID's
DECLARE #OrdersToProcess TABLE (OrderID INT, MemberID INT);
-- Inserts all Orders
INSERT INTO Orders (MemberID, TotalAmount)
OUTPUT inserted.OrderID, inserted.MemberID INTO #OrdersToProcess
SELECT MemberID, 0
FROM [dbo].[Carts]
-- Inserts order details
INSERT INTO OrderDetails (OrderID, ProductID, Quantity)
SELECT OrderID, ProductID, Quantity
FROM [dbo].[Carts] C
INNER JOIN #OrdersToProcess O ON C.MemberID = O.MemberID;
-- Updates order totals
UPDATE [dbo].[Orders]
SET TotalAmount = T.Total FROM
(
SELECT OrderID, SUM(Quantity * Price) AS [Total]
FROM [dbo].[Carts] C
INNER JOIN #OrdersToProcess O ON C.MemberID = O.MemberID
GROUP BY OrderID
) T
WHERE [dbo].[Orders].OrderID = T.OrderID
SELECT * FROM [dbo].[Orders];
SELECT * FROM [dbo].[OrderDetails];
Results:
As I understand your problem, this store procedure should be called when a particular member presses the check-out button, so it should create a single order with all the items in the cart of that member.
You can use something like this:
INSERT INTO Orders (MemberID, TotalAmount)
VALUES (#MemberID, 0)
SET #OrderID=SCOPE_IDENTITY()
INSERT INTO OrderDetails (OrderID, ProductID, Quantity)
SELECT OrderID, ProductID, Quantity
FROM [dbo].[Carts] C
WHERE C.MemberID=#MemberID
UPDATE dbo.Orders SET TotalAmount=(
SELECT SUM(c.Quantity*c.Price)
FROM dbo.Carts c
WHERE c.MemberID=#MemberID
) WHERE OrderID=#OrderID
It's true that this reads the Carts table twice, but with a proper index (on the MemberID column) that should be fast enough.
I Have the following scenario:
CREATE TABLE dbo.Orders
(
OrderID int IDENTITY (1,1) NOT NULL
, OrderVersion int DEFAULT(1)
, Customer varchar(30)
, ScheduleDate date
, PaymentOption int
);
CREATE TABLE dbo.OrdersItems
(
OrderItemsID int IDENTITY (1,1) NOT NULL
, OrderID int
, Product varchar(100)
, Qty int
, value decimal(18,2)
);
CREATE TABLE dbo.logOrders
(
OrderID int NOT NULL
, OrderVersion int DEFAULT(1)
, Customer varchar(30)
, ScheduleDate date
, PaymentOption int
);
CREATE TABLE dbo.logOrdersItems
(
OrderItemsID int NOT NULL
, OrderID int
, Product varchar(100)
, Qty int
, value decimal(18,2)
);
-- Insert values into the table.
INSERT INTO dbo.Orders (Customer , ScheduleDate, PaymentOption)
VALUES ('John', 2016-09-01, 1);
INSERT INTO dbo.OrdersItems( OrderId, Product, Qty, Value)
VALUES (1, 'Foo', 20, 35.658),
(1, 'Bla', 50, 100)
(1, 'XYZ', 10, 3589)
First Statement
UPDATE Orders set ScheduleDate = 2016-10-05 WHERE OrderId = 1
Second Statement
Delete From OrdersItems WHERE OrderItemsID = 2
UPDATE OrdersItems set Qty = 5 WHERE OrderItemsID = 1
Third Statement
Update Orders set PaymentOption = 2 WHERE OrderId = 1
Update OrdersItems set Value = 1050 WHERE OrderItemsID = 3
I am trying to figure out how to make a trigger that after each one of the Statements Sample above Insert on the log Tables the data before the changing. And setting the OrderVersion to OrderVersion + 1 on table Orders.
So on the log Tables I will have all versions after the later one.
Is it possible to make a single trigger to monitor both tables and execute getting the original data before the UPDATE, DELETE , INSERT statement to get the original data and INSERT on the logTables ?
Here comes a sample to explain better what result I want.
This is the Initial Data on table Orders and OrdersItems
If I make an Update on Orders ( any column ) or Make an Update,Insert,Delete on OrdersItems I need to Insert on respectively logTables the data on the image.
And with this I'll have on logOrders and logItems the original data and on the Orders and Items the altered data.
I Hope I could explain better what I mean.
You will need two triggers. The trigger for the Orders table handles Orders table update/delete. The trigger for the OrdersItems table does the same for OrdersItems. The triggers look like this:
For the Orders table:
CREATE TRIGGER dbo.Orders_trigger
ON dbo.Orders
AFTER DELETE,UPDATE
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO dbo.logOrders
SELECT * FROM DELETED;
INSERT INTO dbo.logOrdersItems
SELECT oi.* FROM OrdersItems oi
WHERE oi.OrderID IN (SELECT OrderId FROM DELETED);
END
GO
For OrdersItems:
CREATE TRIGGER dbo.OrdersItems_trigger
ON dbo.OrdersItems
AFTER DELETE,UPDATE
AS
BEGIN
SET NOCOUNT ON;
--Inerst the changed/deleted OrdersItems into the log
INSERT INTO dbo.logOrdersItems
SELECT * FROM DELETED
--Inserts the unchanged sibling OrdersItems records into the log
INSERT INTO dbo.logOrdersItems
SELECT oi.* FROM OrdersItems oi
WHERE oi.OrderId IN (SELECT DISTINCT OrderId FROM DELETED)
AND oi.OrderItemsID NOT IN (SELECT DISTINCT OrderItemsID FROM DELETED);
INSERT INTO dbo.logOrders
SELECT o.* FROM Orders o
WHERE o.OrderID IN (SELECT DISTINCT OrderId FROM DELETED);
END
GO
The Orders Trigger is fairly straightforward. Use the virtual DELETED table to insert the original version of the records into the log. Then join to the child OrdersItems records and insert them into the log as well. The way this is written, it will work even if you update or delete multiple Order records at a time.
The OrdersItems Trigger is a bit more complicated. You need to log the pre-chage version of the OrdersItems and Orders Records. But you also want (I think) to log the unchanged "sibling" OrdersItems records as well so that you have a complete picture of the records.
I know this is just your sample data, but you will want to add some kind of a timestamp to the records in the log tables. Otherwise you just end up with a bunch of duplicate rows and you cannot tell which is which. At the beginning of the trigger you can create a variable to hold the update datetime and then append that to your INSERT statement for the logs.
DECLARE #UpdateDateTime DATETIME;
SET #UpdateDateTime = GETUTCDATE();
I have been trying to Write a Stored Procedure where i can perform UpSert using Merge with the Following Condition
If Record is Present then change EndDate of Target to Yesterday's day i.e., Present Day - 1
If Record is not Present then Insert New Record
Here is the Table tblEmployee i used in SP
CREATE TABLE tblEmployee
(
[EmployeeID] [int] IDENTITY(1,1) NOT NULL,
[Name] [varchar](10) NOT NULL,
[StartDate] [date] NOT NULL,
[EndDate] [date] NOT NULL
)
Here is my SP which Takes UDTT as Input parameter
CREATE PROCEDURE [dbo].[usp_UpsertEmployees]
#typeEmployee typeEmployee READONLY -- It has same column like tblEmployye except EmployeeID
AS
BEGIN
SET NOCOUNT ON;
MERGE INTO tblEmployee AS TARGET
USING #typeEmployee AS SOURCE
ON TARGET.Name = SOURCE.Name
WHEN MATCHED and TARGET.StartDate < SOURCE.StartDate
THEN
--First Update Existing Record EndDate to Previous Date as shown below
UPDATE
set TARGET.EndDate = DATEADD(day, -1, convert(date, SOURCE.StartDate))
-- Now Insert New Record
--INSERT VALUES(SOURCE.Name, SOURCE.StartDate, SOURCE.EndDate);
WHEN NOT MATCHED by TARGET
THEN
INSERT VALUES(SOURCE.Name, SOURCE.StartDate, SOURCE.EndDate);
SET NOCOUNT OFF;
END
How can i perform both Updating Existing Record and Adding New Record When Column is matched
Can Please someone Explain me the Execution Flow of Merge in TSQL i.e.,
WHEN MATCHED --Will this Execute Everytime
WHEN NOT MATCHED by TARGET -- Will this Execute Everytime
WHEN NOT MATCHED by SOURCE -- Will this Execute Everytime
Will all above 3 condition get executed everytime in Merge or only Matching condition is executed Everytime
Thanks in Advance
This isn't what MERGE is meant to do (update and insert in same clause). To accomplish this, you can use the OUTPUT clause to get all the updated records only. The MERGE/OUTPUT combo is very picky. Your OUTPUT updates are really the TARGET records that got updated, so you have to start the TARGET records in a temp/table variable. Then you match those back against the SOURCE to do the INSERT. You won't be allowed to join the output results directly back to source or even use as a correlated subquery within the WHERE.
Setup some sample data
The code below just sets up some sample data.
-- Setup sample data
DECLARE #typeEmployee TABLE (
[Name] [varchar](10) NOT NULL,
[StartDate] [date] NOT NULL,
[EndDate] [date] NOT NULL
)
DECLARE #tblEmployee TABLE (
[EmployeeID] [int] IDENTITY(1,1) NOT NULL,
[Name] [varchar](10) NOT NULL,
[StartDate] [date] NOT NULL,
[EndDate] [date] NOT NULL
)
INSERT #tblEmployee VALUES ('Emp A', '1/1/2016', '2/1/2016')
INSERT #typeEmployee VALUES ('Emp A', '1/5/2016', '2/2/2016'), ('Emp B', '3/1/2016', '4/1/2016')
Updates to Stored Procedure
You can use OUTPUT at the end of a MERGE to have it return the modified records of the target records, and by including $action, you will also get whether it was an insert, update, or delete.
However, the result set from MERGE / OUTPUT cannot be directly joined against the SOURCE table so you can do your INSERT since you only get the TARGET records back. You can't use the results of the OUTPUT within correlated sub-query from the SOURCE table either. Easiest thing is to use a temp table or table variable to capture the output.
-- Logic to do upsert
DECLARE #Updates TABLE (
[Name] [varchar](10) NOT NULL,
[StartDate] [date] NOT NULL,
[EndDate] [date] NOT NULL
)
INSERT #Updates
SELECT
Name,
StartDate,
EndDate
FROM (
MERGE INTO #tblEmployee AS TARGET
USING #typeEmployee AS SOURCE
ON TARGET.Name = SOURCE.Name
WHEN MATCHED AND TARGET.StartDate < SOURCE.StartDate
THEN
--First Update Existing Record EndDate to Previous Date as shown below
UPDATE SET
EndDate = DATEADD(DAY, -1, CONVERT(DATE, SOURCE.StartDate))
WHEN NOT MATCHED BY TARGET -- OR MATCHED AND TARGET.StartDate >= SOURCE.StartDate -- Handle this case?
THEN
INSERT VALUES(SOURCE.Name, SOURCE.StartDate, SOURCE.EndDate)
OUTPUT $action, INSERTED.Name, INSERTED.StartDate, INSERTED.EndDate
-- Use the MERGE to return all changed records of target table
) AllChanges (ActionType, Name, StartDate, EndDate)
WHERE AllChanges.ActionType = 'UPDATE' -- Only get records that were updated
Now that you've captured the output of the MERGE and filtered to only get updated TARGET records, you can then do your outstanding INSERT by filtering only the SOURCE records that were part of the MERGE update.
INSERT #tblEmployee
SELECT
SOURCE.Name,
SOURCE.StartDate,
SOURCE.EndDate
FROM #typeEmployee SOURCE
WHERE EXISTS (
SELECT *
FROM #Updates Updates
WHERE Updates.Name = SOURCE.Name
-- Other join conditions to ensure 1:1 match against SOURCE (start date?)
)
Ouput
This is the output of the sample records after the change. Your intended TARGET changes were made.
-- Show output
SELECT * FROM #tblEmployee
Following the idea from the accepted answer, this works as well in Sql server 2008 r2:
create table Test1 (
Id int, FromDate date, ThruDate date, Value int
)
insert into dbo.Test1
(Id, FromDate, ThruDate, [Value])
select
t.Id, t.FromDate, T.ThruDate, t.Value * 100
from (
MERGE dbo.Test1 AS Target
USING (
select 1 as Id, '2000-01-01' as FromDate, '2000-12-31' as ThruDate, 2 as Value
) AS Source
ON ( target.id = source.Id
)
WHEN MATCHED
THEN
UPDATE SET Target.[Id] = Source.[Id]
, Target.[FromDate] = Source.[FromDate]
, Target.[ThruDate] = Source.[ThruDate]
, Target.[Value] = Source.[Value]
WHEN NOT MATCHED BY TARGET
THEN
INSERT ([Id]
, [FromDate]
, [ThruDate]
, [Value])
VALUES (Source.[Id]
, Source.[FromDate]
, Source.[ThruDate]
, Source.[Value])
OUTPUT $ACTION as Act, Inserted.*
) t
where t.Act = 'Update'
You can play with different values.