I have 2 tables, Contact and ContactFunction. They have a One to Many relationship, as in a Contact can have many ContactFunctions. So ContactFunction has ContactID as a foreign key. So in my .NetCore web application I can update a contact and their functions on the same page. But this is in fact saving two separate entities, named above. I have a trigger on the Contact table that puts inserts/updates/deletes into a history table. I need a way to also include any ContactFunction changes in this trigger too. But at this stage it seems there has been no commits yet to the ContactFunction table, because of the relationship I assume. Can you think of a way to do this other than creating a history table for both?
I'm trying to read the data from the ContactFunction table inside the Contact table trigger, but it's not there yet. the variable #FUNCTIONSSTRING is where I'm trying to read data from the second table (ContactFunction)
USE [DI_Business_IE_EF_Development04]
GO
/****** Object: Trigger [dbo].[ContactHistoryLog] Script Date: 25/01/2019 09:33:00 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TRIGGER [dbo].[ContactHistoryLog] ON [dbo].[Contact]
AFTER INSERT,UPDATE, DELETE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #CurrentDateTime datetime2 = SYSUTCDATETIME();
DECLARE #Action varchar(10);
DECLARE #CONTACTID INT;
DECLARE #FIRSTUPDATE INT;
DECLARE #FUNCTIONSSTRING VARCHAR(250) = '';
DECLARE #INSERTEDID INT;
IF EXISTS (SELECT 1 FROM inserted)
BEGIN
IF EXISTS (SELECT 1 FROM deleted)
BEGIN
SET #Action = 'UPDATE'
END
ELSE
BEGIN
SET #Action = 'INSERT'
END
END
ELSE
BEGIN
SET #Action = 'DELETE'
END
/* Update start times for newly updated data */
UPDATE c
SET
StartDate = #CurrentDateTime, #CONTACTID = c.ID
FROM
dbo.Contact c
INNER JOIN inserted i
ON c.Id = i.Id
SELECT #FIRSTUPDATE = COUNT(*) FROM dbo.ContactHistory WHERE ContactId = #CONTACTID
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
SELECT
#FUNCTIONSSTRING = #FUNCTIONSSTRING + F.Name + ','
FROM ContactFunction CF
JOIN [Function] F ON F.ID = CF.FunctionID
WHERE CF.ContactID = #CONTACTID
INSERT INTO DBO.ContactHistory(
ContactId,
CompanyID,
Title,
Forename,
Surname,
JobTitle,
ExecutiveNumber,
IsInformationProvider,
Email,
UpdatedBy,
StartDate,
EndDate,
[Action],
FunctionNames
)
SELECT
I.Id,
I.CompanyID,
I.Title,
I.Forename,
I.Surname,
I.JobTitle,
I.ExecutiveNumber,
I.IsInformationProvider,
I.Email,
I.UpdatedBy,
#CurrentDateTime,
#CurrentDateTime,
#Action,
(SELECT SUBSTRING(#FUNCTIONSSTRING, 0, LEN(#FUNCTIONSSTRING)))AS FunctionNames
FROM
dbo.Contact c
RIGHT JOIN inserted I
ON c.Id = I.Id
-- if this is first update insert deleted value as well as inserted value, else just put in inserted
IF (#FIRSTUPDATE = 0)
BEGIN
INSERT INTO DBO.ContactHistory(
ContactId,
CompanyID,
Title,
Forename,
Surname,
JobTitle,
ExecutiveNumber,
IsInformationProvider,
Email,
UpdatedBy,
StartDate,
EndDate,
[Action],
FunctionNames
)
SELECT
d.Id,
d.CompanyID,
d.Title,
d.Forename,
d.Surname,
d.JobTitle,
d.ExecutiveNumber,
d.IsInformationProvider,
d.Email,
d.UpdatedBy,
d.StartDate,
ISNULL(d.StartDate,#CurrentDateTime),
'NONE',
(SELECT SUBSTRING(#FUNCTIONSSTRING, 0, LEN(#FUNCTIONSSTRING)))AS FunctionNames
FROM
dbo.Contact c
RIGHT JOIN deleted d
ON c.Id = d.Id
END
END
I would expect to see the latest changes to the ContactFunction table, but it's not there. is there a way to do this?
Related
I'm having troubles while trying to update a record when it is being inserted into a database via external application.
I need the record being inserted from the users in the application takes a value MinimumStock from a table AERO_LOCATIONSTOCKMIN on which are the parent location and the asset type
LOCATIONSTOCKMINID ASSETTYPEID LOCATIONID MINIMUMSTOCK
54000000001 54000000043 43200000357 12.00
54000000002 54000000043 43200000883 6.00
This is the purpose: When a user tries to insert a record on Asset table (via external app), the trigger must to check if the user has selected a location for that Asset. If so, the trigger must retrieve the parent location for the selected location and check if there is stock minimum defined for this AssetType and for this parent location.
If all is fulfilled, the trigger must to set the field UDFText01 to the minimum stock defined in the above table.
Here is what I've tried so far. The trigger is checking well the requirements, but it fails when trying to UPDATE and raising the following error:
The transaction ended in the trigger. The batch has been aborted.
I tried to modify the UPDATE statement in many ways but all of them raises the same error message.
EDIT
Following #Sean Lannge's suggestion I've edited my trigger in order to manage more than one inserts. The trigger is not longer showing the error message but the changes (insert) are not saved into database.
ALTER TRIGGER [spectwosuite].[TRI_ASSET_STOCKMIN] ON [spectwosuite].[ASSET]
FOR INSERT AS
BEGIN
IF (UPPER(USER) != 'SPECTWOREPLENG')
DECLARE #assettypeid numeric(15);
DECLARE #assetid numeric(15);
DECLARE #locationid numeric(15);
DECLARE #parentlocationid numeric(15);
DECLARE #stockmin numeric(9,2);
DECLARE #mistock varchar(100);
DECLARE crs_ROWS CURSOR FOR
SELECT ASSETID, ASSETTYPEID, LOCATIONID
FROM inserted;
OPEN crs_ROWS;
FETCH NEXT FROM crs_ROWS INTO #assetid, #assettypeid, #locationid
WHILE (##FETCH_STATUS = 0)
BEGIN
SELECT #parentlocationID = ParentLocationID FROM Location
LEFT JOIN Asset ON Asset.LocationID = Location.LocationID
WHERE Asset.LocationID = #locationid;
IF NOT EXISTS
(SELECT * FROM Location LEFT JOIN INSERTED AS i ON Location.LocationID = i.LocationID
WHERE Location.LocationID = i.LocationID)
BEGIN
RAISERROR ('Please fill the Location for the Asset.',16,1);
ROLLBACK;
END
ELSE
IF EXISTS (SELECT MinimumStock FROM AERO_LOCATIONSTOCKMIN
WHERE AssetTypeID = #assettypeid AND LocationID = (SELECT ParentLocationID FROM Location WHERE LocationID = #locationID))
BEGIN
SELECT #stockmin = MinimumStock FROM AERO_LOCATIONSTOCKMIN
WHERE AssetTypeID = #assetTypeID AND LocationID =
(SELECT ParentLocationID FROM Location WHERE LocationID = #locationID);
SELECT #mistock= CONVERT(varchar(100),#stockmin);
--RAISERROR (#mistock,16,1);
UPDATE spectwosuite.ASSET
SET UDFText01 = #mistock
FROM
INSERTED I
INNER JOIN spectwosuite.ASSET T ON
T.AssetID = I.AssetID
--UPDATE spectwosuite.ASSET SET UDFText01 = #mistock
-- FROM spectwosuite.ASSET AS A INNER JOIN inserted AS I
-- ON A.AssetID = I.AssetID;
END
ELSE
RAISERROR ('The parent Location for the Asset Type doesn't have minimum stock defined',16,1);
ROLLBACK;
FETCH NEXT FROM crs_ROWS INTO #assetid, #assettypeid, #locationid;
END;
CLOSE crs_ROWS;
DEALLOCATE crs_ROWS;
END;
Based on the extended discussion here see if something like this isn't a bit closer to what you are trying to do.
ALTER TRIGGER [spectwosuite].[TRI_ASSET_STOCKMIN] ON [spectwosuite].[ASSET] FOR INSERT AS
IF (UPPER(USER) != 'SPECTWOREPLENG')
BEGIN
create table #MyInsertedCopy
(
--whatever columns go here that you want to display for error rows
ErrorMessage varchar(50)
)
insert #MyInsertedCopy
select i.* --use your real columns, not *
, 'Please fill the Location for the Asset' as ErrorMessage
from inserted i
left join Location l on l.LocationID = i.LocationID
where l.LocationID IS NULL
insert #MyInsertedCopy
select i.* --use your real columns, not *
, 'The parent location for that Asset doesn''t have minimum stock defined' as ErrorMessage
from inserted i
left join AERO_LOCATIONSTOCKMIN a on a.AssetTypeID = i.AssetID
left join Location l on l.ParentLocationID = i.LocationID
where a.AssetTypeID is NULL
update a
set UDFText01 = MinimumStock
FROM INSERTED I
INNER JOIN spectwosuite.ASSET T ON T.AssetID = I.AssetID
INNER JOIN AERO_LOCATIONSTOCKMIN a on a.AssetTypeID = i.AssetID
INNER JOIN Location l on l.ParentLocationID = i.LocationID
IF EXISTS(select * from #MyInsertedCopy)
--do something to report that there are rows that failed
select * from #MyInsertedCopy
END
Need help with a trigger.
If I remove my inner joins from where clause it runs, so I'm guessing this is where the issue would be. If I use this where clause it works "WHERE oeordhdr_sql.A4GLIdentity =#id "
Any help would be appreciated. Below is the code...
ALTER trigger [dbo].[bdordermakeonhold]
on [dbo].[oeordhdr_sql]
for insert
as
declare #edi as char(1)
declare #id as numeric(9,0)
declare #ordno as char(8)
declare #ordtype as char(1)
declare #cus_no as char(12)
declare #status as char(1)
declare #ship_to_addr_3 as char(40)
set #edi=(select edi_fg from inserted)
set #id=(select a4glidentity from inserted)
set #ordno=(select ord_no from inserted)
set #ordtype=(select ord_type from inserted)
set #cus_no=(select cus_no from inserted)
set #status=(select status from inserted)
set #ship_to_addr_3=(select status from inserted)
set nocount on
if #edi is null or #edi='E'
if not exists (SELECT ID, Cus_No, State, CertificateID, Expiration FROM BDSalesTaxCerts
WHERE State = 'FL' AND #cus_no = Cus_No)
begin
UPDATE oeordhdr_sql
SET status = 'C',hold_fg = 'H'
FROM inserted AS i INNER JOIN
oeordlin_sql AS LN ON LN.ord_no = i.ord_no INNER JOIN
ARCUSFIL_SQL as CS ON i.cus_no = CS.cus_no
WHERE oeordhdr_sql.A4GLIdentity =#id and cs.state not in ('FL','PR',' ','lima') and
(LN.loc = 'SE') AND (#ship_to_addr_3 LIKE '%FL%') AND #status in ('1')
if not exists (select ID from BDInvoiceSupport where Orig_Ord_no=#ordno and orig_ord_type
=#ordtype)
begin
INSERT INTO [dbo].[BDInvoiceSupport]
([ID]
,[LastUpdate]
,[RecordRevLevel]
,[Inv_No]
,[TaxProcessingStatus]
,[Orig_Ord_no]
,[orig_ord_type]
,[InvoiceSent]
,[OnHoldReason])
select NEWID(),GETDATE(),0,inv_no,0,ord_no,ord_type,0,'Not checked yet.'
from inserted
end
end
If two rows get inserted at once, this would give you problems. You should write it with joins to the inserted table, instead of using variables.
I have defined a trigger on a table that is triggered
AFTER INSERT, DELETE, UPDATE
There are cases where the trigger fires, with both INSERTED AND DELETED tables being empty. How can this be possible?
For the records, that's the trigger
CREATE TRIGGER [dbo].[AuditUsersTrigger] ON [dbo].[Users]
AFTER INSERT, DELETE, UPDATE
AS
BEGIN
SET NOCOUNT ON
DECLARE #type nchar(1), #hasChanges bit
SET #hasChanges = 1
IF EXISTS (SELECT * FROM INSERTED)
IF EXISTS (SELECT * FROM DELETED)
BEGIN
SELECT #type = 'U'
IF EXISTS (
SELECT *
FROM INSERTED i
INNER JOIN DELETED d ON
i.Name = d.Name AND
i.Pwd = d.Pwd AND
...
) SELECT #hasChanges = 0
END
ELSE
SELECT #type = 'I'
ELSE
SELECT #type = 'D'
IF #type = 'D' OR (#type = 'U' AND #hasChanges = 1)
BEGIN
INSERT AuditUsers (
New, Id, Name, ...
)
SELECT
0, Id, Name, ...
FROM DELETED
IF #type = 'D'
BEGIN
INSERT AuditUsers (New)
SELECT 1
END
END
IF #type = 'I' OR (#type = 'U' AND #hasChanges = 1)
BEGIN
IF #type = 'I'
BEGIN
INSERT AuditUsers (New)
SELECT 0
END
INSERT AuditUsers (
New, Id, Name, ...
)
SELECT
0, Id, Name, ...
FROM INSERTED
END
IF Trigger_Nestlevel() < 2
BEGIN
DECLARE #clientId TABLE (id INT)
DECLARE #clientCode NVARCHAR(50), #shopId INT;
IF #type = 'I' OR #type = 'U'
BEGIN
SELECT #clientCode = ClientCode, #shopId = ShopId FROM INSERTED;
INSERT INTO #clientId SELECT id FROM Clients WHERE code = #clientCode;
IF NOT EXISTS (SELECT 1 FROM #clientId)
BEGIN
INSERT Clients (name, code, active, shopId) OUTPUT INSERTED.id INTO #clientId
VALUES (#clientCode, #clientCode, 1, #shopId);
END
UPDATE Users SET ClientId = (SELECT TOP 1 id FROM #clientId) WHERE ClientCode = #clientCode;
END
END
END
This is documented behaviour
DML triggers execute when a user tries to modify data through a data manipulation language (DML) event. DML events are INSERT, UPDATE, or DELETE statements on a table or view. These triggers fire when any valid event is fired, regardless of whether or not any table rows are affected.
If you have a recurring loop, whereby table A has a trigger that affects table B, and table B has a trigger that affects table A, you can manage this using TRIGGER_NESTLEVEL, or by checking if either inserted or deleted contain any rows before actually doing anything.
I've got a table that stores the following:
JobID
ValidationItemID
CreatedBy
I want to be able to insert into this table (a predefined template) but only add rows that dont exist. What I mean by dont exist is the combination of JobID and ValidationItemID make the row unique. My procedure passes in a JobID, but I cannot pass in a validation item ID as I pull this column as part of the template...
Something to this effect:
CREATE PROCEDURE insTemplate
#JobID varchar(50),
#Login varchar(50)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
INSERT INTO
ValidationItemSignOff
(
JobID,
ValidationItemID,
CreatedBy
)
SELECT
DISTINCT
#JobID,
vi.ValidationItemID,
#Login
FROM
RunOffAnswer roa
INNER JOIN
Method m ON m.MethodID = roa.MethodID
INNER JOIN
RunOffValidationItem vi ON vi.ValidationItemID = m.ValidationItemID
WHERE
vi.Inactive=0
AND NOT EXISTS(SELECT * FROM ValidationItemSignOff WHERE JobID=#JobID AND vi.ValidationItemID ???
END
GO
I dont know how to phrase the where condition so that it doesn't reinsert the same JobID and ValidationItemID. Lets say I have inside the table:
Job ValidationItem
Job A 1
Job A 2
Job A 5
And I have a template with the following:
ValidationItem
1
2
3
4
5
6
When I run my stored procedure it should only insert values 3,4,6 from the template table, for the job id... So I need help with my where condition.
I think my issue is I cannot use NOT EXISTS, maybe I need to join back to this ValidationItemSignOff table itself on JobID and ValidationItemID where ValidationItemID is NULL, maybe like this:
CREATE PROCEDURE insTemplate
#JobID varchar(50),
#Login varchar(50)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
INSERT INTO
ValidationItemSignOff
(
JobID,
ValidationItemID,
CreatedBy
)
SELECT
DISTINCT
#JobID,
vi.ValidationItemID,
#Login
FROM
RunOffAnswer roa
INNER JOIN
Method m ON m.MethodID = roa.MethodID
INNER JOIN
RunOffValidationItem vi ON vi.ValidationItemID = m.ValidationItemID
RIGHT OUTER JOIN
ValidationItemSignOff viso ON viso.JobID = #JobID
AND viso.ValidationItemID = vi.ValidationItemID
WHERE
vi.Inactive=0
AND viso.ValidationItemID IS NULL
END
GO
Got it I think
Replacing the right join with this:
LEFT JOIN
ValidationItemSignOff viso
ON viso.JobID = #JobID
AND viso.ValidationItemID = vi.ValidationItemID
If you're on 2008 or above...
MERGE INTO ValidationItemSignOff As Target
USING (SELECT DISTINCT #JobID, vi.ValidationItemID, #Login
FROM RunOffAnswer roa INNER JOIN
Method m
ON m.MethodID = roa.MethodID INNER JOIN
RunOffValidationItem vi
ON vi.ValidationItemID = m.ValidationItemID
WHERE vi.Inactive = 0) As Source (JobID, ValidationItemID, Login)
ON Target.JobID = Source.JobID
AND Target.ValidationItemID = Source.ValidationItemID
WHEN NOT MATCHED BY TARGET THEN
INSERT (JobID, ValidationItemID, CreatedBy)
VALUES (Source.JobID, Source.ValidationItemID, Source.Login);
Disclaimer: I may have not got the syntax spot on here.
I got it with this:
ALTER PROCEDURE insSignOffTemplate
#JobID varchar(50),
#Login varchar(50)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
INSERT INTO
ValidationItemSignOff
(
JobID,
ValidationItemID,
CreatedBy
)
SELECT
DISTINCT
#JobID,
vi.ValidationItemID,
#Login
FROM
RunOffAnswer roa
INNER JOIN
Method m
ON
m.MethodID = roa.MethodID
INNER JOIN
RunOffValidationItem vi
ON
vi.ValidationItemID = m.ValidationItemID
LEFT JOIN
ValidationItemSignOff viso
ON viso.JobID = #JobID AND viso.ValidationItemID = vi.ValidationItemID
WHERE
vi.Inactive=0
AND viso.ValidationItemID IS NULL
END
GO
I have a SQL Server database and I need to manually do an update query. There for no solutions using any programming language can be used.(stored procedures can be used)
I have 4 tables affected (/used) in the query.
[Orders]
[StatusHistoryForOrder]
[StatusHistory]
[Statuses]
I need to update the field [Orders].[OrderStatusID] which is a foreign key to [Statuses]. (So actually changing the state of the order. The table [StatusHistoryForOrder] is a linking table to [StatusHistory] and only contains 2 colums.
[StatusHistoryForOrder].[OrderId]
[StatusHistoryForOrder].[OrderStatusHistoryid]
Don't say that this is not logically cause I already know that. The company who designed the database is a complete retarded company but the database is now too large to set things straight and there is neither the time or money to do it.
The [StatusHistory] table has multiple columns:
[StatusHistory].[OrderStatusHistoryId]
[StatusHistory].[OrderStatusId]
[StatusHistory].[Date]
[StatusHistory].[Message]
The [StatusHistory].[OrderStatusId] is also a foreign key to [Statuses].
In the update query I need to update the status of the order to status 16. But only on rows that now have status 1 and are older then 60 days. I know I can check the date by using the function
DATEDIFF(DD,[StatusHistory].[Date],GETDATE()) > 60
But how to implement this query if the date field is not in the orders. And to set the new [StatusHistory] a new row has to be made for that table and the [StatusHistoryForOrder] table also needs a new row and the ID of that row needs to be set in the [Orders] table row.
Does anyone know how to do this? I am fairly new to SQL Server (or SQL for that matter) and I have absolutly no clue where to begin.
Conclusion:
I need a stored procedure that first checks every row in [Orders] if the [StatusHistory].[Date] (which is linked to the order using foreign keys) of that order is older that 60. If it is older then a new StatusHistory row must be inserted with the current date and status 16. Then in [StatusHistoryForOrder] a new row must be inserted with the new ID of the statusHistory been set in [StatusHistoryForOrder].[OrderStatusHistoryid] and the order id set in [StatusHistoryForOrder].[OrderId]. And last but not least: The [Orders].[OrderStatusID] also needs to be set to 16.
A select query to select the date and status of the order:
SELECT TOP (100) PERCENT
dbo.Orders.OrderID,
dbo.Statuses.Description AS Status,
dbo.StatusHistory.Date
FROM
dbo.Orders
INNER JOIN
dbo.Statuses
ON
dbo.Orders.OrderStatusID = dbo.Statuses.StatusId
INNER JOIN
dbo.StatusHistoryForOrder
ON
dbo.Orders.OrderID = dbo.StatusHistoryForOrder.OrderId
INNER JOIN
dbo.StatusHistory
ON
dbo.StatusHistoryForOrder.OrderStatusHistoryid = dbo.StatusHistory.OrderStatusHistoryId
WHERE
(dbo.Statuses.StatusId = 1)
AND
(DATEDIFF(DD, dbo.StatusHistory.Date, GETDATE()) > 60)
UPDATE
For #marc_s:
Can anyone help me with that?
Try this CTE (Common Table Expression) to find all those orders - does it work, are the results plausible? (this doesn't update anything just yet - just SELECTing for now):
USE (your database name here)
GO
DECLARE #OrdersToUpdate TABLE (OrderID INT, StatusHistoryID INT, StatusDate DATETIME)
;WITH RelevantOrders AS
(
SELECT
o.OrderId, sh.Date
FROM dbo.Orders o
INNER JOIN dbo.StatusHistoryForOrder ho ON ho.OrderId = o.OrderId
INNER JOIN dbo.StatusHistory sh ON ho.OrderStatusHistoryid = sh.OrderStatusHistoryid
WHERE
sh.Date <= DATEADD(D, -60, GETDATE()) -- older than 60 days back from today
AND o.OrderStatusID = 1 -- status = 1
)
INSERT INTO #OrdersToUpdate(OrderID, StatusDate)
SELECT OrderID, [Date]
FROM RelevantOrders
BEGIN TRANSACTION
BEGIN TRY
DECLARE #OrderIDToInsert INT, -- OrderID to process
#InsertedStatusHistoryID INT -- new ID of the inserted row in StatusHistory
-- grab the first OrderID that needs to be processed
SELECT TOP 1 #OrderIDToInsert = OrderID
FROM #OrdersToUpdate
WHERE StatusHistoryID IS NULL
ORDER BY OrderID
-- as long as there are still more OrderID to be processed ....
WHILE #OrderIDToInsert IS NOT NULL
BEGIN
PRINT 'Now inserting new StatusHistory entry for OrderID = ' + CAST(#OrderIDToInsert AS VARCHAR(10))
INSERT INTO dbo.StatusHistory(OrderStatusID, [Date], [Message])
VALUES(16, GETDATE(), 'Bulk Insert/Update operation') -- enter here whatever you want to store
SELECT #InsertedStatusHistoryID = SCOPE_IDENTITY(); -- grab newly inserted ID
PRINT 'New StatusHistory entry inserted with ID = ' + CAST(#InsertedStatusHistoryID AS VARCHAR(10))
UPDATE #OrdersToUpdate
SET StatusHistoryID = #InsertedStatusHistoryID
WHERE OrderID = #OrderIDToInsert
-- safety - reset #OrderIDToInsert to NULL so that we'll know when we're done
SET #OrderIDToInsert = NULL
-- read next OrderID to be processed
SELECT TOP 1 #OrderIDToInsert = OrderID
FROM #OrdersToUpdate
WHERE StatusHistoryID IS NULL
ORDER BY OrderID
END
-- insert into the StatusHistoryForOrder table
INSERT INTO dbo.StatusHistoryForOrder(OrderID, OrderStatusHistoryID)
SELECT OrderID, StatusHistoryID
FROM #OrdersToUpdate
-- update your Orders to status ID = 16
UPDATE dbo.Orders
SET OrderStatusID = 16
FROM #OrdersToUpdate upd
WHERE dbo.Orders.OrderID = upd.OrderID
COMMIT TRANSACTION
END TRY
BEGIN CATCH
SELECT
ERROR_NUMBER() AS ErrorNumber,
ERROR_SEVERITY() AS ErrorSeverity,
ERROR_STATE() AS ErrorState,
ERROR_PROCEDURE() AS ErrorProcedure,
ERROR_LINE() AS ErrorLine,
ERROR_MESSAGE() AS ErrorMessage
ROLLBACK TRANSACTION
END CATCH
This CTE basically joins your Orders table to the StatusHistory table (via the intermediate link table) and selects the values you're interested in (hopefully!).
This particular problem seems solvable with set operations only.
DECLARE #Orders TABLE (ID int, rownum int IDENTITY);
DECLARE #StatusHistory TABLE (ID int, rownum int IDENTITY);
/* get the list of orders with expired statuses */
INSERT INTO #Orders (ID)
SELECT o.OrderID
FROM Orders o
INNER JOIN StatusHistoryForOrder shfo ON o.OrderID = shfo.OrderId
INNER JOIN StatusHistory sh ON shfo.OrderStatusHistoryid = sh.OrderStatusHistoryId
GROUP BY o.OrderID
HAVING DATEDIFF(DD, MAX(sh.Date), GETDATE()) > 60
/* add so many new rows to StatusHistory and remember the new IDs */
INSERT INTO StatusHistory (OrderStatusId, Date, Message)
OUTPUT inserted.OrderStatusHistoryId INTO #StatusHistory (ID)
SELECT
16,
GETDATE(),
'Auto-inserted as the previous status has expired'
FROM #Orders
/* join the two temp lists together and add rows to StatusHistoryForOrder */
INSERT INTO StatusHistoryForOrder (OrderId, OrderStatusHistoryid)
SELECT o.ID, sh.ID
FROM #Orders o
INNER JOIN #StatusHistory sh ON o.rownum = sh.rownum
/* finally update the statuses in Orders */
UPDATE Orders
SET OrderStatusID = 16
FROM #Orders o
WHERE Orders.OrderID = o.ID
This should be the body of a single transaction, of course.