trigger to allow multiple updates - sql

I have this trigger I want to make it allow multiple row updates, currently it handling only single row update.when i update record, it says sub query returning more then 1 value..
GO
ALTER TRIGGER [dbo].[OnReceiptUpdate]
ON [dbo].[paymentReceipt]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
Declare #correctdate VARCHAR(19);
Declare #receiptNo VARCHAR(50);
DECLARE #customerID NCHAR(50)
SET #customerID= (SELECT customerID FROM inserted)
set #correctdate = (SELECT CONVERT(VARCHAR(19),paymentDate,103) FROM inserted)
set #receiptNo = (SELECT receiptNo FROM inserted)
BEGIN
UPDATE Paymentreceipt
SET paymentDate = #correctdate
WHERE customerID = #customerID and receiptNo=#receiptNo
END
END

Update p
Set p.paymentDate = CONVERT(VARCHAR(19),i.paymentDate,103)
From Paymentreceipt p
inner join inserted i
On p.customerID = i.customerID and p.receiptNo = i.receiptNo
should do it I think.
PS why is p.paymentdate a string? That's asking for it.

The easiest way is to use an update statement such as the one below in the Trigger.
UPDATE Paymentreceipt
SET paymentDate = CONVERT(VARCHAR(19),paymentDate,103)
FROM inserted
WHERE Inserted.receiptNo = Paymentreceipt.receiptNo
AND Inserted.customerID = Paymentreceipt.customerID
Note I don't have SQL server in front of me so the syntax might not be 100% correct but that gives you the general idea.
In general I try and avoid triggers but if your really need the trigger then use it but it may be possible to address this issue through the use of a stored procedure.

Related

Trigger After Update Returns Null

I wrote the following trigger to update two columns in my table when any record is inserted or updated:
CREATE TRIGGER test
ON mytable
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
UPDATE t
SET UPDATE_TIMESTAMP = GETDATE(),
UPDATE_USER_NAME = SUSER_SNAME()
FROM mytable t
INNER JOIN inserted i ON t._ID = i._ID
END
GO
However, it does not update the columns UPDATE_TIMESTAMP and UPDATE_USER_NAME and still returns the default null. Could you please help me how to fix that? Thanks!
I think that you are facing a recursing call in your trigger
Try with this :
CREATE TRIGGER test
ON mytable
AFTER INSERT,UPDATE
AS
BEGIN
SET NOCOUNT ON;
IF TRIGGER_NESTLEVEL() > 1
RETURN;
UPDATE t
SET UPDATE_TIMESTAMP = GETDATE(),
UPDATE_USER_NAME = SUSER_SNAME()
FROM mytable t
INNER JOIN inserted i ON t._ID = i._ID
END
GO

TSQL Cursor calls Stored Procedure 'View Nesting Level Exceeded'

Being a bit of a SQL Newb, I tried to create a SQL script to execute a stored procedure on a number of rows using a cursor. I found the code to create the cursor and the stored procedure works as expected.
However, sometimes during execution I get the error
Maximum stored procedure, function, trigger, or view nesting level exceeded (limit 32)
Here is the SQL script in question. Any ideas why I get this error?
CREATE PROCEDURE p_MigrateRenewalOptions
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #OrderId VARCHAR(255) = NULL
DECLARE #RenewalId INT = 0
DECLARE #DiscountCode VARCHAR(255) = NULL
DECLARE #UpgradeCode VARCHAR(255) = NULL
DECLARE #ProductCode VARCHAR(255) = NULL
DECLARE rCursor CURSOR FOR
SELECT RenewalId FROM t_Renewals WHERE DiscountCode IS NOT NULL AND UpgradeCode IS NOT NULL
OPEN rCursor
FETCH NEXT FROM rCursor INTO #RenewalId
-- Iterate over t_Renewals with DiscountCode, UpgradeCode
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #OrderId = OrderId from t_Renewals where RenewalId = #RenewalId
SELECT #DiscountCode = DiscountCode from t_Renewals where RenewalId = #RenewalId
SELECT #UpgradeCode = UpgradeCode from t_Renewals where RenewalId = #RenewalId
SELECT #ProductCode = ProductCode from t_Order Where OrderId = #OrderId
-- Create renewal options for the t_Renewal entry
EXEC p_SelectOrCreateRenewalOptions #OrderId
-- Migrate the DiscountCode, UpgradeCode from the renewal record
UPDATE t_Renewal_Option
SET CouponCode = #DiscountCode
WHERE RenewalId = #RenewalId AND OptionType = 0 AND CouponCode IS NULL
UPDATE t_Renewal_Option
SET CouponCode = #UpgradeCode
WHERE RenewalId = #RenewalId AND OptionType = 1 AND CouponCode IS NULL
-- NULL the Renewal record DiscountCode, UpgradeCode
UPDATE t_Renewals
SET DiscountCode = NULL, UpgradeCode = NULL
WHERE RenewalId = #RenewalId
FETCH NEXT FROM rCursor INTO #RenewalId
END
CLOSE rCursor
DEALLOCATE rCursor
END
GO
One thing I noted, that the statement
-- Create renewal options for the t_Renewal entry
EXEC p_SelectOrCreateRenewalOptions #OrderId
returns a set, e.g. if you run this code outside of a stored procedure, inside SQL Server Management Studio, you get several sets of results in the output window. Is this the cause? Is it possible to dump the data as I don't need the returned rows from p_SelectOrCreateRenewalOptions
EDIT: As requested, here is the code for p_SelectOrCreateRenewalOptions
CREATE PROCEDURE p_SelectOrCreateRenewalOptions
(
#OrderId VARCHAR(255) = NULL
)
AS
BEGIN
SET NOCOUNT ON
DECLARE #ProductCode VARCHAR(255);
SELECT #ProductCode = ProductCode FROM t_Order WHERE OrderId = #OrderId;
DECLARE #StaticOptionCount INT;
SET #StaticOptionCount = dbo.f_QueryRenewalOptionCount(#ProductCode);
DECLARE #OptionCount INT;
SELECT #OptionCount = COUNT(*) FROM t_Renewal_Option ro INNER JOIN t_Renewals r ON r.RenewalId = ro.RenewalId WHERE r.OrderId = #OrderId
IF (#OptionCount != #StaticOptionCount)
BEGIN
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRANSACTION
SELECT #OptionCount = COUNT(*) FROM t_Renewal_Option ro INNER JOIN t_Renewals r ON r.RenewalId = ro.RenewalId WHERE r.OrderId = #OrderId
IF (#OptionCount != #StaticOptionCount)
BEGIN
DECLARE #RenewalId INT;
SELECT #RenewalId = RenewalId FROM t_Renewals r WHERE r.OrderId = #OrderId;
DELETE FROM t_Renewal_Option WHERE RenewalId = #RenewalId;
INSERT INTO t_Renewal_Option (RenewalId, OptionProductCode, OptionType, LastUpdated)
SELECT #RenewalId, sro.OptionProductCode, sro.OptionTypeId, GETDATE()
FROM t_Static_Renewal_Option sro
WHERE sro.ProductCode = #ProductCode
END
COMMIT TRANSACTION
END
SELECT ro.* FROM t_Renewal_Option ro INNER JOIN t_Renewals r ON r.RenewalId = ro.RenewalId WHERE r.OrderId = #OrderId
END
GO
f_QueryRenewalOptionCount simply does a select:
-- f_QueryRenewalOptionCount
SELECT #Count = COUNT(*) FROM t_Static_Renewal_Option o WHERE o.ProductCode = #ProductCode
Triggers wise, I don't think we have any triggers. There is a trigger on the t_Order table on INSERT but apart from that, nothing else.
UPDATE 12-SEP-14:
This 'works' but I don't understand the problem fully. I wrapped the EXEC call into a Begin/End Transaction
Note this script is called once to migrate some old part of the schema to a new part. Its not performance intensive and just has to 'work once'
Do you have a trigger on any of your tables? If an update trigger updates its own table, it will trigger itself again, which will update the table again, etc.
Do you use view or function anywhere in your code? Views can call on other views or functions, which call other views / functions.
The cursor here, while makes my skin crawl, is likely not the cause of the error your seeing. Without being able to see what the nested stored procedure is doing, this is just an educated guess, but that's probably where the issue lies.
The error you're seeing happens whenever a recursive SQL operation basically gets stuck in an infinite loop. The two ways I have had it happen is with a poorly written recursive CTE (basically a table which infinitely unions to itself) or when, as is more likely in this case, a store procedure calls a stored procedure which calls a stored procedure... and so on down the line. For instance (and I haven't tested this) if p_selectOrCreateRenewalOptions called p_MigrateRenewalOptions, you'd probably see a similar error.

After Update Trigger puzzlement

I am trying to get my head round an AFTER UPDATE trigger.
Currently in our DB there is a Trigger that contains a cursor. From my understanding cursors in triggers are generally bad performing, so I'm trying to get rid of the cursor.
Currently the trigger looks like this:
ALTER TRIGGER [dbo].[trg_TaskMovement_Zone] ON [dbo].[Tasks_Movement]
AFTER INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON
DECLARE #rowcheck int
DECLARE #MovementID INT
DECLARE #SiteFromID INT
DECLARE #SiteToID INT
DECLARE #SiteResponsibleID INT
DECLARE #FromAddress_Postcode Varchar(20)
DECLARE #ToAddress_Postcode Varchar(20)
DECLARE zcursor CURSOR FOR SELECT ID, SiteFromID, SiteToID, SiteResponsibleID
, FromAddress_Postcode, ToAddress_Postcode FROM inserted
OPEN zcursor
SELECT #rowcheck=1
WHILE #rowcheck=1
BEGIN
FETCH NEXT FROM zcursor INTO #MovementID, #SiteFromID, #SiteToID, #SiteResponsibleID, #FromAddress_Postcode, #ToAddress_Postcode
IF (##FETCH_STATUS = 0)
BEGIN
UPDATE Tasks_Movement
SET ZoneFromID = dbo.fn_GetZoneFromPostcode(#FromAddress_Postcode),
ZoneToID = dbo.fn_GetZoneFromPostcode(#ToAddress_Postcode)
WHERE Tasks_Movement.ID = #MovementID
UPDATE Tasks_Movement
SET SiteResponsibleID = [dbo].[fn_GetDefaultDepotResponsibleForSite](#SiteFromID)
WHERE Tasks_Movement.ID = #MovementID
AND (#SiteResponsibleID Is NULL OR #SiteResponsibleID=0)
AND (#SiteFromID Is NOT NULL AND #SiteFromID>0)
UPDATE Tasks_Movement
SET SiteResponsibleID = [dbo].[fn_GetDefaultDepotResponsibleForSite](#SiteToID)
WHERE Tasks_Movement.ID = #MovementID
AND (#SiteResponsibleID Is NULL OR #SiteResponsibleID=0)
AND (#SiteToID Is NOT NULL AND #SiteToID>0)
END
ELSE
SELECT #rowcheck=0
END
CLOSE zcursor
DEALLOCATE zcursor
END
From what I can tell the cursor in this is completely unnecessary(?)
Would I be right in thinking that the following would work better:
ALTER TRIGGER [dbo].[trg_TaskMovement_Zone] ON [dbo].[Tasks_Movement]
AFTER INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON
UPDATE Tasks_Movement
SET ZoneFromID = dbo.fn_GetZoneFromPostcode(inserted.FromAddress_Postcode),
ZoneToID = dbo.fn_GetZoneFromPostcode(inserted.ToAddress_Postcode)
FROM inserted
WHERE Tasks_Movement.ID IN (SELECT id FROM inserted)
UPDATE Tasks_Movement
SET SiteResponsibleID = [dbo].[fn_GetDefaultDepotResponsibleForSite](inserted.SiteFromID)
FROM inserted
WHERE Tasks_Movement.ID IN (SELECT id FROM inserted
WHERE (inserted.SiteResponsibleID Is NULL OR inserted.SiteResponsibleID=0)
AND (inserted.SiteFromID Is NOT NULL AND inserted.SiteFromID>0))
UPDATE Tasks_Movement
SET SiteResponsibleID = [dbo].[fn_GetDefaultDepotResponsibleForSite](#SiteToID)
FROM inserted
WHERE Tasks_Movement.ID IN (SELECT id FROM inserted
WHERE (inserted.SiteResponsibleID Is NULL OR inserted.SiteResponsibleID=0)
AND (inserted.SiteToID Is NOT NULL AND inserted.SiteToID>0))
END
I think your trigger should be something like this:
ALTER TRIGGER [dbo].[trg_TaskMovement_Zone] ON [dbo].[Tasks_Movement]
AFTER INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON
UPDATE tm
SET ZoneFromID = dbo.fn_GetZoneFromPostcode(i.FromAddress_Postcode),
ZoneToID = dbo.fn_GetZoneFromPostcode(i.ToAddress_Postcode)
FROM Tasks_Movement tm
INNER JOIN inserted i
ON i.ID = tm.ID;
UPDATE tm
SET SiteResponsibleID = [dbo].[fn_GetDefaultDepotResponsibleForSite](i.SiteFromID)
FROM Tasks_Movement tm
INNER JOIN inserted i
ON i.ID = tm.ID
WHERE (i.SiteResponsibleID IS NULL OR i.SiteResponsibleID = 0)
AND i.SiteFromID > 0
UPDATE tm
SET SiteResponsibleID = [dbo].[fn_GetDefaultDepotResponsibleForSite](i.SiteToID)
FROM Tasks_Movement tm
INNER JOIN inserted i
ON i.ID = tm.ID
WHERE (i.SiteResponsibleID IS NULL OR i.SiteResponsibleID = 0)
AND i.SiteToID > 0
END
I've changed it to use SQl Server's UPDATE .. FROM syntax, and also removed the redundant null check when you are checking if a site ID > 0. NULL is not greater than or less than 0, so if SiteID is null SiteID > 0 can never evaluate to true, so it is a redundant additional check.
Finally, I would also recommend removing the user defined functions, although I can't see under the hood of these, based on the name they look very much like they are simple loukup functions that could be achived much more efficiently with joins.
EDIT
Rather than using the UPDATE(column) function I would add an additional join to the update to filter for updated rows, e.g.:
UPDATE tm
SET SiteResponsibleID = [dbo].[fn_GetDefaultDepotResponsibleForSite](i.SiteToID)
FROM Tasks_Movement tm
INNER JOIN inserted i
ON i.ID = tm.ID
LEFT JOIN deleted d
ON d.ID = i.ID
WHERE (i.SiteResponsibleID IS NULL OR i.SiteResponsibleID = 0)
AND i.SiteToID > 0
AND AND ISNULL(i.SiteToID, 0) != ISNULL(d.SiteToID);
I'd do it this way because UPDATE(siteToID) will return true if any row has an updated value, so if you update 1,000,000 rows and one has a change it will perform the update on all of them, not just the ones that have changed, by joining to deleted you can limit the update to relevant rows.

Creating a Stored Procedure

I am trying to create a stored procedure that will allow me to update the ActualArrivalDate and ActualDepartureDate in a TripStop table where StopTypeCode = Drop in the TripStopOrder table.
Create Proc spUpdateTable
#OrderID int, #ArrivalDate datetime, #DepartureDate datetime
As
Begin
Begin Transaction
Select * From dbo.TripStopOrder
Join dbo.TripStop ON dbo.TripStopOrder.TripStopID = dbo.TripStop.TripStopID
Where OrderID = ''and StopTypeCode ='Drop'
Once I find this record I need to grab the TripStopId and pass it into the Update statement.
Not sure how to this...can I use a temp table then run another Select statement to pick up the TripStopID?
Update dbo.TripStop SET ArrivalDate='',DepartureDate=''
Where TripStopID = ''
End
Commit
Any ideas or suggestions would be greatly appreciated. ~Newbie~
You can assign the value to a variable such as #TripStopId:
DECLARE #TripStopId INT
Select #TripStopid = TripStopId
From dbo.TripStopOrder
Join dbo.TripStop ON dbo.TripStopOrder.TripStopID = dbo.TripStop.TripStopID
Where OrderID = ''and StopTypeCode ='Drop'
Then you can use it in your UPDATE statement.
Update dbo.TripStop SET ArrivalDate='',DepartureDate=''
Where TripStopID = #TripStopId
Instead of doing the SELECT and then the UPDATE, just change your WHERE clause in your UPDATE statement:
WHERE TripStopID = (SELECT T.TripStopID FROM TripStopOrder O
INNER JOIN TripStop T ON O.TripStopID = T.TripStopID
WHERE OrderID = #OrderID AND StopTypeCode = 'Drop')

Modify SQL trigger to work when inserted table contains more than one row

I've got a SQL trigger written for a table in SQL Server 2008. It works well when there is only one row in the 'inserted' table. How can I modify this trigger to work correctly when there are multiple rows? Performance is key here, so I'd like to stay away from cursors, temp tables, etc. (if possible).
Essentially the trigger checks to see if either the 'ClientID' or 'TemplateID' fields were changed. If they were, and the OriginalClientID or OriginalTemplateID fields are null, it populates them (thus setting the OriginalXXX fields once and only once so I can always see what the first values were).
CREATE TRIGGER [dbo].[trigSetOriginalValues]
ON [dbo].[Review]
FOR INSERT, UPDATE
AS
BEGIN
IF (NOT UPDATE(TemplateID) AND NOT UPDATE(ClientID)) return
DECLARE #TemplateID int
DECLARE #OriginalTemplateID int
DECLARE #ClientID int
DECLARE #OriginalClientID int
DECLARE #ReviewID int
SET #ReviewID = (SELECT ReviewID FROM inserted)
SET #ClientID = (SELECT ClientID FROM inserted)
SET #TemplateID = (SELECT TemplateID FROM inserted)
SET #OriginalTemplateID = (SELECT OriginalTemplateID FROM inserted);
SET #OriginalClientID = (SELECT OriginalClientID FROM inserted);
IF (#OriginalTemplateID IS NULL AND #TemplateID IS NOT NULL)
BEGIN
UPDATE [dbo].[Review] SET OriginalTemplateID = #TemplateID WHERE ReviewID=#ReviewID
END
IF (#OriginalClientID IS NULL AND #ClientID IS NOT NULL)
BEGIN
UPDATE [dbo].[Review] SET OriginalClientID = #ClientID WHERE ReviewID=#ReviewID
END
END
This should be your trigger:
UPDATE A
SET A.OriginalTemplateID = B.TemplateID
FROM [dbo].[Review] A
INNER JOIN INSERTED B
ON A.ReviewID = B.ReviewID
WHERE A.OriginalTemplateID IS NULL AND B.TemplateID IS NOT NULL
UPDATE A
SET A.OriginalClientID = B.ClientID
FROM [dbo].[Review] A
INNER JOIN INSERTED B
ON A.ReviewID = B.ReviewID
WHERE A.OriginalClientID IS NULL AND B.ClientID IS NOT NULL
Though you could still do this on a single UPDATE, but with a more complicated filter.