Update inserted record on a trigger - sql-server

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

Related

How to read uncommitted data from other table inside trigger

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?

Why does my stored procedure return -1?

I have a stored procedure. The call to it returns -1. What does this mean?
Here is the code that calls the stored procedure (auto-generated by Entity Framework):
public virtual int DeleteProjectData(Nullable<int> projectId, string deleteType, string username)
{
var projectIdParameter = projectId.HasValue ?
new ObjectParameter("projectId", projectId) :
new ObjectParameter("projectId", typeof(int));
var deleteTypeParameter = deleteType != null ?
new ObjectParameter("deleteType", deleteType) :
new ObjectParameter("deleteType", typeof(string));
var usernameParameter = username != null ?
new ObjectParameter("username", username) :
new ObjectParameter("username", typeof(string));
int result = ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction("DeleteProjectData",
projectIdParameter, deleteTypeParameter, usernameParameter);
return result; // result is -1
}
Here is the stored procedure:
ALTER PROCEDURE [dbo].[DeleteProjectData]
#projectId INT,
#deleteType VARCHAR(10),
#username NVARCHAR(255)
AS
SET NOCOUNT ON
BEGIN TRY
DECLARE #realProjectId INT = NULL,
#isTemplate BIT,
#ErrorMessage NVARCHAR(4000),
#deleteTypeAll VARCHAR(10),
#deleteTypeNodes VARCHAR(10)
DECLARE #usernameBinary VARBINARY(255)
SET #usernameBinary = CAST(#username AS VARBINARY(255))
SET CONTEXT_INFO #usernameBinary
SELECT #deleteTypeAll = 'All',
#deleteTypeNodes = 'Nodes'
-- ensure the input project exists and is not a template
SELECT #realProjectId = ProjectId
FROM Project
WHERE ProjectId = #projectId
IF #deleteType NOT IN (#deleteTypeAll, #deleteTypeNodes)
BEGIN
-- create a the error message for a project that does not exist
SELECT #ErrorMessage = N'The input Delete Type is not valid. Valid values are: ''' + #deleteTypeAll
+ ''' and ''' + #deleteTypeNodes + ''''
-- raise the error
RAISERROR (#ErrorMessage, 16, 1)
END
IF #realProjectId IS NULL
BEGIN
-- create a the error message for a project that does not exist
SELECT #ErrorMessage = N'The input ProjectId does not have a corresponding Project record. No calculations will
be performed. ProjectId: ' + CAST(#projectId AS VARCHAR)
-- raise the error
RAISERROR (#ErrorMessage, 16, 1)
END
BEGIN TRANSACTION
-- If deleting all, delete all reports associated with project first:
IF #deleteType = #deleteTypeAll
BEGIN
DELETE [dbo].[ReportComment]
WHERE ReportId in (SELECT ReportId
FROM [dbo].[Report]
WHERE ProjectId = #realProjectId)
DELETE [dbo].[ReportMetric]
WHERE ReportId in (SELECT ReportId
FROM [dbo].[Report]
WHERE ProjectId = #realProjectId)
DELETE [dbo].[ReportTopN]
WHERE ReportId in (SELECT ReportId
FROM [dbo].[Report]
WHERE ProjectId = #realProjectId)
DELETE [dbo].[Report]
WHERE ReportId in (SELECT ReportId
FROM [dbo].[Report]
WHERE ProjectId = #realProjectId)
END
ELSE
BEGIN
DELETE [dbo].[ReportTopN]
WHERE ReportId in (SELECT ReportId
FROM [dbo].[Report]
WHERE ProjectId = #realProjectId)
END
IF #deleteType IN (#deleteTypeAll, #deleteTypeNodes)
BEGIN
DECLARE #Keys TABLE (
ProjectId INT,
NodeId INT,
DeviationId INT,
CauseId INT,
ConsequenceId INT,
SafeguardId INT,
RecommendationId INT,
RemarkId INT,
DrawingId INT,
RiskDataId INT,
BowtieLoopId INT,
BowtieId INT)
-- build a list of keys associated with the project (simplifies delete queries)
INSERT INTO #Keys (ProjectId,
NodeId,
DeviationId,
CauseId,
ConsequenceId,
SafeguardId,
RecommendationId,
RemarkId,
DrawingId,
RiskDataId,
BowtieLoopId,
BowtieId)
SELECT p.ProjectId,
n.NodeId,
d.DeviationId,
ca.CauseId,
co.ConsequenceId,
s.SafeguardId,
r.RecommendationId,
re.RemarkId,
dr.DrawingId,
rd.RiskDataId,
bl.BowtieLoopId,
b.BowtieId
FROM Project p
LEFT OUTER JOIN Node n
ON p.ProjectId = n.ProjectId
LEFT OUTER JOIN Deviation d
ON n.NodeId = d.NodeId
LEFT OUTER JOIN Cause ca
ON d.DeviationId = ca.DeviationId
LEFT OUTER JOIN Consequence co
ON ca.CauseId = co.CauseId
LEFT OUTER JOIN Safeguard s
ON co.ConsequenceId = s.ConsequenceId
LEFT OUTER JOIN Recommendation r
ON co.ConsequenceId = r.ConsequenceId
LEFT OUTER JOIN Remark re
ON co.ConsequenceId = re.ConsequenceId
LEFT OUTER JOIN Drawing dr
ON r.RecommendationId = dr.RecommendationId
LEFT OUTER JOIN Bowtie b
ON b.ProjectId = p.ProjectId
LEFT OUTER JOIN BowtieLoop bl
ON bl.BowtieId = b.BowtieId
LEFT Outer JOIN RiskData rd
ON rd.BowtieLoopId = bl.BowtieLoopId
WHERE p.ProjectId = #realProjectId
-- delete the data that was imported
DELETE FROM Drawing
WHERE DrawingId IN (SELECT DISTINCT DrawingId
FROM #Keys)
DELETE FROM Recommendation
WHERE RecommendationId IN (SELECT DISTINCT RecommendationId
FROM #Keys)
DELETE FROM Safeguard
WHERE SafeguardId IN (SELECT DISTINCT SafeguardId
FROM #Keys)
DELETE FROM Remark
WHERE RemarkId IN (SELECT DISTINCT RemarkId
FROM #Keys)
DELETE FROM Consequence
WHERE ConsequenceId IN (SELECT DISTINCT ConsequenceId
FROM #Keys)
DELETE FROM CauseToBowtieLoopDetails
Where CauseId IN (Select Distinct CauseId
FROM #Keys)
DELETE FROM Cause
WHERE CauseId IN (SELECT DISTINCT CauseId
FROM #Keys)
DELETE FROM Deviation
WHERE DeviationId IN (SELECT DISTINCT DeviationId
FROM #Keys)
DELETE FROM Node
WHERE NodeId IN (SELECT DISTINCT NodeId
FROM #Keys)
DELETE FROM RiskData
WHERE RiskDataId IN (SELECT DISTINCT RiskDataId
FROM #Keys)
DELETE FROM BowtieLoop
WHERE BowtieLoopId IN (SELECT DISTINCT BowtieLoopId
FROM #Keys)
DELETE FROM BowtieToEquipmentLookup
WHERE BowtieId IN (SELECT DISTINCT BowtieId
FROM #Keys)
DELETE FROM Bowtie
WHERE BowtieId IN (SELECT DISTINCT BowtieId
FROM #Keys)
DELETE FROM ProjectDeviation
WHERE ProjectId = #realProjectId
DELETE FROM ProjectSafeguard
WHERE ProjectId = #realProjectId
DELETE FROM ProjectRecommendation
WHERE ProjectId = #realProjectId
-- also delete from the metrics tables
DELETE FROM ProjectMetrics
WHERE ProjectId = #realProjectId
DELETE FROM CauseMetrics
WHERE ProjectId = #realProjectId
DELETE FROM ConsequenceMetrics
WHERE ProjectId = #realProjectId
DELETE FROM RecommendationMetrics
WHERE ProjectId = #realProjectId
DELETE FROM SafeguardMetrics
WHERE ProjectId = #realProjectId
END
IF #deleteType = #deleteTypeAll
BEGIN
-- delete the project specific data (i.e., data not imported)
DELETE FROM RiskMatrixAxis
WHERE ProjectId = #realProjectId
DELETE FROM SafeRecCategory
WHERE ProjectId = #realProjectId
DELETE FROM Participant
WHERE ProjectId = #realProjectId
DELETE FROM RiskRanking
WHERE ProjectId = #realProjectId
DELETE FROM Category
WHERE ProjectId = #realProjectId
DELETE FROM ImportFile
WHERE ProjectId = #realProjectId
DELETE FROM Project
WHERE ProjectId = #realProjectId
END
COMMIT TRANSACTION
END TRY
BEGIN CATCH
IF(##TRANCOUNT > 0)
BEGIN
-- rollback all changes if any error occurred
ROLLBACK TRANSACTION
END
-- raise the original error
EXEC RethrowError;
END CATCH
As you can see, the stored procedure doesn't return anything. I'm told that if it doesn't return anything, the Entity Framework call to run the stored procedure will return the number of rows. So what would it mean that it returns -1? Am I right in interpreting -1 as an error?
From the Microsoft doc on ExecuteNonQuery (here):
For UPDATE, INSERT, and DELETE statements, the return value is the
number of rows affected by the command. When a trigger exists on a
table being inserted or updated, the return value includes the number
of rows affected by both the insert or update operation and the number
of rows affected by the trigger or triggers. For all other types of
statements, the return value is -1. If a rollback occurs, the return
value is also -1.
Since you are calling a proc, but trying to treat it like a function, I assume that retrieving it's return value results in the same behavior.

Insert Statement not working for Insert trigger

I have a trigger that executes off the back of a stored procedure, to capture certain data changes and inserts, for audit purposes.
There is a stored procedure that adds rows to table DTA, the trigger is coded to fire from this as such;
CREATE TRIGGER [AUDIT_TRACE]
ON [DTA]
AFTER UPDATE, INSERT
AS BEGIN
SET NOCOUNT ON;
BEGIN TRANSACTION
IF EXISTS (SELECT * FROM sys.tables WHERE name = 'tmp_inserted')
DROP TABLE tmp_inserted
IF EXISTS (SELECT * FROM sys.tables WHERE name = 'tmp_deleted')
DROP TABLE tmp_deleted
SELECT * INTO tmp_inserted from inserted
SELECT * INTO tmp_deleted from deleted
INSERT INTO [AUDIT_TRAIL]
SELECT
UpdatedDate
,UserName
,Name
,oldValue
,newValue
,DATATABLEID
,ISNULL(AuthInvNo,'')+ISNULL(invNO,'') as InvoiceNumber
,AuthAccount As Product
,AuthValue AS Value
,QTY
,InputScreen
FROM
(
SELECT
i.UpdatedDate as [UpdatedDate]
,psn.UserName as [Username]
,CONCAT(psn.Firstname,' ',psn.surname) as [Name]
,CONVERT(nvarchar(36),i.DataTableId) as [DataTableID]
,dtType.Description as [InputScreen]
,dtat.Description as [ColumnName]
,CONVERT(nvarchar(1000),dtText.Text) as [Entry]
,dtavB.Description as OldValue
,dtavA.Description as NewValue
FROM dt
INNER JOIN inserted i on i.DataTableId = dt.DataTableId
LEFT JOIN deleted d on d.DataTableId = i.DataTableId
INNER JOIN dtavA on dtavA.DataTableAttributeValueId = i.DataTableAttributeValueId
and dtavA.DataTableAttributeTypeId IN ('23087D97-B96B-4015-9E66-258EE7CAF499','2D5E9D64-A2B6-444D-938A-7D8DD66208E0')-- after
LEFT JOIN dtavB on dtavB.DataTableAttributeValueId = d.DataTableAttributeValueId
and dtavB.DataTableAttributeTypeId IN ('23087D97-B96B-4015-9E66-258EE7CAF499','2D5E9D64-A2B6-444D-938A-7D8DD66208E0')-- before
INNER JOIN dtText on dtText.DataTableId = i.DataTableId
INNER JOIN dtType on dtType.DataTableTypeId = dt.DataTableTypeID
INNER JOIN psn on psn.PersonId = i.UpdatedBy
INNER JOIN dtat on dtat.DataTableAttributeTypeId = dtText.DataTableAttributeTypeId
)E
PIVOT(MAX([ENTRY]) FOR [COLUMNNAME] IN(DEBITCREDIT,AuthValue,QTY,AuthAccount,AuthInvNo,InvNO))as p
COMMIT TRANSACTION
END
Now the problem is that when inserting data into the DTA table nothing is being inserted into our AUDIT_TRAIL table, but when a row is updated in the DTA table, the outcome is exactly what we expect, oldValue, NewValue and all. As far as my colleagues and I can tell there is nothing wrong with the query, we have used profiler traces and all the sections are executing as they should. When running the code manually to select from the tmp_Inserted and tmp_Deleted tables, so we can see what the values we are dealing with are, there is again no issue. To further complicate things, when the INSERT INTO statement is run on its own then the newly inserted line appears as we would expect.
In order to ensure that data is not filtered by the JOINs in insert statement within trigger, insert data from INSERTED by joining it with all tables in inline INSERT.
SELECT I.* INTO tmp_inserted
FROM dt
INNER JOIN inserted i on i.DataTableId = dt.DataTableId
LEFT JOIN deleted d etc..

MSSQL Trigger - Updating newly inserted record on INSERT

I wish to make a modification (Set Deleted = 1) to rows being inserted into my table CustomerContact if the SELECT statement returns more than 0.
I have the following, but it remains untested:
CREATE TRIGGER mark_cust_contact_deleted ON CustomerContact
AFTER INSERT AS
BEGIN
DECLARE #numrows INT;
/* Determine if order matches criteria for marking customer contact as DELETED immediately */
SELECT #numrows = COUNT(*)
FROM [Order] o
JOIN OrderMeterDetail om
ON o.OrderID = om.OrderID
WHERE o.WorkTypeID = 3 AND o.WorkActionID = 26 AND o.WorkStageID IN (109, 309, 409)
AND om.MeterDetailTypeID = 1 AND om.MeterLocationID IN (2, 4)
AND o.orderid IN (SELECT OrderID FROM INSERTED);
/* If the order matches the criteria, mark the customer contact as deleted */
IF (#numrows >= 1)
UPDATE CustomerContact
SET Deleted = 1
WHERE CustomerContactID IN (SELECT CustomerContactID FROM INSERTED);
END
Within my IF statement, I am using FROM INSERTED, assuming that this will return the newly inserted id for the record that was created by the insert.
I have two questions about this statement:
Will this part of the statement perform an UPDATE just the record
that was just inserted into CustomerContact?
UPDATE CustomerContact
SET Deleted = 1
WHERE CustomerContactID IN (SELECT CustomerContactID FROM INSERTED);
Is this the way that would be deemed correct to make a change to a row that has just been inserted based on the result of a SELECT statement?
CustomerContactID is an auto-incrementing primary key column.
You say "Just the record that was inserted". Inserted can contain more than one record. If there is only one, then your trigger will function as you expect. But if there is more than one, it won't.
I would rewrite your logic into a single update statement along the lines of...
Update CustomerContact
Set Deleted = 1
From CustomerContact
inner join inserted on CustomerContact.CustomerContactID = inserted.CustomerContactID
inner join orders on inserted.OrderID = orders.OrderID
where
-- some criteria.
CREATE TRIGGER mark_cust_contact_deleted ON CustomerContact
AFTER INSERT AS
BEGIN
DECLARE #numrows INT;
/* Determine if order matches criteria for marking customer contact as DELETED immediately */
-- Get all the records into a temp table
SELECT * INTO #Temp
FROM inserted
Declare #ID int;
SELECT #numrows = COUNT(*)
FROM [Order] o
JOIN OrderMeterDetail om
ON o.OrderID = om.OrderID
WHERE o.WorkTypeID = 3 AND o.WorkActionID = 26 AND o.WorkStageID IN (109, 309, 409)
AND om.MeterDetailTypeID = 1 AND om.MeterLocationID IN (2, 4)
AND o.orderid IN (SELECT OrderID FROM #Temp);
IF (#numrows >= 1)
BEGIN
WHILE EXISTS (SELECT TOP 1 * FROM #Temp)
BEGIN
SELECT TOP 1 #ID = ID FROM #Temp
/* If the order matches the criteria, mark the customer contact as deleted */
UPDATE CustomerContact
SET Deleted = 1
WHERE CustomerContactID IN (SELECT CustomerContactID FROM #Temp WHERE ID = #ID);
DELETE FROM #Temp WHERE ID = #ID
END
END
DROP TABLE #Temp
END
I think you can do something like this, tweak the code to futher suit for needs, hope this will help.
Here is the final solution that I used to solve this issue:
CREATE TRIGGER mark_cust_contact_deleted ON CustomerContact
AFTER INSERT AS
BEGIN
UPDATE CustomerContact
SET Deleted = 1
FROM CustomerContact cc
JOIN inserted i
ON cc.CustomerContactID = i.CustomerContactID
JOIN [Order] o
ON i.OrderID = o.OrderID
JOIN OrderMeterDetail om
ON i.OrderID = om.OrderID
WHERE o.WorkTypeID = 3 AND o.WorkActionID = 26 AND o.WorkStageID IN (109, 309, 409)
AND om.MeterDetailTypeID = 1 AND om.MeterLocationID IN (2, 4)
END

SQL Server UPDATE with WHERE spanning over 2 tables

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.

Resources