Will creating Variable whenever needed in Stored Procedure or Function or Trigger helps in performance optimization?
Which one is better of below or both have same performance?
Option 1:
CREATE TRIGGER [dbo].[UpdateAmount] ON [RequestDB].[dbo].[Invoice]
AFTER UPDATE
AS
BEGIN
IF UPDATE(Service_Amount)
BEGIN
DECLARE #NewService_Amount float,#OldService_Amount float //Var Created When needed
SELECT #NewService_Amount = I.Service_Amount FROM INSERTED I
SELECT #OldService_Amount = D.Service_Amount FROM DELETED D
IF (#NewService_Amount <> #OldService_Amount)
BEGIN
SELECT #InvId = I.Id FROM INSERTED I
DECLARE #DiffService_Amount float //Var Created When needed
SET #DiffService_Amount = #NewService_Amount - #OldService_Amount
UPDATE [RequestDB].[dbo].[Request] SET Actual_Amount = #DiffService_Amount WHERE Invoice_Id = #InvId
END
END
END
Option 2:
CREATE TRIGGER [dbo].[UpdateAmount] ON [RequestDB].[dbo].[Invoice]
AFTER UPDATE
AS
BEGIN
DECLARE #NewService_Amount float,#OldService_Amount float.#DiffService_Amount float //All Var Created at once on top of code
IF UPDATE(Service_Amount)
BEGIN
SELECT #NewService_Amount = I.Service_Amount FROM INSERTED I /*For New UPDATE Value: INSERTED. For Old BEFORE UPDATE Valie: DELETED*/
SELECT #OldService_Amount = D.Service_Amount FROM DELETED D
IF (#NewService_Amount <> #OldService_Amount)
BEGIN
SELECT #InvId = I.Id FROM INSERTED I
SET #DiffService_Amount = #NewService_Amount - #OldService_Amount
UPDATE [RequestDB].[dbo].[Request] SET Actual_Amount = #DiffService_Amount WHERE Invoice_Id = #InvId
END
END
END
The docs don't get too specific about variables other than to say that once it's declared it's available through that batch process:
The scope of a variable lasts from the point it is declared until the end of the batch or stored procedure in which it is declared.
My assumption would be declaring it later is better (given how they word the docs)--if you don't use it, avoid declaring it. However, the real answer would be to test it and profile it. Whichever works better in practice would be the real solution, IMHO.
I also hope this isn't a premature optimization. If you're down to declaration order to make your scripts run faster, you're probably looking in the wrong spot.
Related
I have the following stored procedure, which is intended to iterate through a list of strings, which contains several substrings of the form prefix.bucketName. I want to iterate through each string and each bucket name, and replace the old prefix with a new prefix, but keep the same bucket name.
To give an example, consider this original string:
"(OldPrefix.BucketA)(OldPrefix.BucketB)"
So for example I would like to get:
"(NewPrefix.BucketA)(NewPrefix.BucketB)"
What I actually get is this:
"(OldPrefix.BucketA)(NewPrefix.BucketB)"
So, in general, only one of the prefixes get updated, and it is not predictable which one. Based on some investigation I have done, it appears that both replacements actually work, but only the last one is actually saved. It seems like SQL should be locking this column but instead, both are read at the same time, the replace is applied, and then both are written, leaving the last write as what shows in the column.
Here is the query - All variable names have been changed for privacy - Some error handling and data validation code was left out for brevity:
DECLARE #PrefixID INT = 1478,
DECLARE #PrefixName_OLD NVARCHAR(50) = 'OldPrefix',
DECLARE #PrefixName_NEW NVARCHAR(50) = 'NewPrefix'
BEGIN TRAN
-- Code to rename the section itself here not shown for brevity
UPDATE
dbo.Component
SET
AString= REPLACE(AString,'('+#Prefix_OLD+'.'+b.BucketName+')', '('+#PrefixName_NEW+'.'+b.BucketName+')'),
FROM
dbo.Component sc
JOIN
dbo.ComponentBucketFilterInString fis
ON
sc.ComponentID = fis.ComponentID
JOIN
dbo.Buckets b
ON
fis.BucketID = b.BucketID
WHERE
b.PrefixID = #PrefixID
COMMIT
RETURN 1
When I write the same query using a while loop, it performs as expected:
DECLARE #BucketsToUpdate TABLE
(
BucketID INT,
BucketName VARCHAR(256)
)
INSERT INTO #BucketsToUpdate
SELECT BucketID, BucketName
FROM Buckets WHERE PrefixID = #PrefixID
WHILE EXISTS(SELECT 1 FROM #BucketsToUpdate)
BEGIN
DECLARE #currentBucketID INT,
#currentBucketName VARCHAR(256)
SELECT TOP 1 #currentBucketID = bucketID, #currentBucketName = bucketName FROM #BucketsToUpdate
UPDATE
dbo.Component
SET
AString = REPLACE(AString,'('+#PrefixName_OLD+'.'+#currentBucketName+')', '('+#PrefixName_NEW+'.'+#currentBucketName+')')
FROM
dbo.Component sc
JOIN
dbo.ComponentBucketFilterInString fis
ON
sc.ComponentID = fis.ComponentID
WHERE fis.BucketID = #currentBucketID
DELETE FROM #BucketsToUpdate WHERE BucketID = #currentBucketID
END
Why does the first version fail? How can I fix it?
The problem you are experiencing is "undefined" behavior when there is more than single match possible for UPDATE FROM JOIN.
In order to make your update possible you should run it multiple times updating one pair of values at a time as you proposed in your second code demo.
Related: How is this script updating table when using LEFT JOINs? and Let’s deprecate UPDATE FROM!:
SQL Server will happily update the same row over and over again if it matches more than one row in the joined table, >>with only the result of the last of those updates sticking<<.
Not sure why you are making the whole process so complex. May be I am not clearly understanding the requirement. As per my understanding, you are looking to update only Prefix part for column 'AString' in the table dbo.Component. Current value for example is-
(OldPrefix.BucketA)(OldPrefix.BucketB)
You wants to update the value as-
(NewPrefix.BucketA)(NewPrefix.BucketB)
Am I right? If yes, you can update all records with a simple Update script as below-
DECLARE #PrefixID INT = 1478
DECLARE #PrefixName_OLD NVARCHAR(50) = 'OldPrefix'
DECLARE #PrefixName_NEW NVARCHAR(50) = 'NewPrefix'
UPDATE Component
SET AString= REPLACE(AString,#PrefixName_OLD,#PrefixName_NEW)
I am using SQL Server 2008 and trying to create a statement which will update a single value within a row from another table if a certain parameter is met. I need to make this as simple as possible for a member of my team to use.
So in this case I want to store 2 values, the Sales Order and the reference. Unfortunately the Sales order has a unique identifier that I need to record and enter into the jobs table and NOT the actual sales order reference.
The parameter which needs to be met is that the Sales order unique identifier cannot exist anywhere in the sales order column within the jobs table. I can get this to work when the while value is set to 1 but not when it's set to 0 and in my head it should be set to 0. Anyone got any ideas why this doesn't work?
/****** Attach an SO to a WO ******/
/****** ONLY EDIT THE VALUES BETWEEN '' ******/
Declare #Reference nvarchar(30);
Set #Reference = 'WO16119';
Declare #SO nvarchar(30);
Set #SO = '0000016205';
/****** DO NOT ALTER THE CODE BEYOND THIS POINT!!!!!!!!!!!!! ******/
/* store more values */
Declare #SOID nvarchar(30);
Set #SOID = (Select SOPOrderReturnID
FROM Test_DB.dbo.SOTable
Where DocumentNo = #SO);
/* check if update should run */
Declare #Check nvarchar (30);
Set #Check = (Select case when exists (select *
from Test_DB.dbo.Jobs
where SalesOrderNumber != #SOID)
then CAST(1 AS BIT)
ELSE CAST(0 AS BIT) End)
While (#Check = 0)
/* if check is true run code below */
Begin
Update Test_DB.dbo.jobs
SET SalesOrderNumber = (Select SOPOrderReturnID
FROM Test_DB.dbo.SOPOrderReturn
Where DocumentNo = #SO)
Where Reference = #Reference
END;
A few comments. First in order to avoid getting into a never ending loop you may want to change your while for an IF statement. You aren't changing the #check value so that will run forever:
IF (#Check = 0)
BEGIN
/* if check is true run code below */
Update Test_DB.dbo.jobs
SET SalesOrderNumber = (Select SOPOrderReturnID
FROM Test_DB.dbo.SOPOrderReturn
Where DocumentNo = #SO)
Where Reference = #Reference
END
Then, without knowing your application I would say that the way you make checks is going to require you to lock your tables to avoid other users invalidating the results of your SELECTs.
I would go instead to creating a UNIQUE constraint over the column you want to be unique and handle the error gracefully. This way you don't need to create big locks on your tables
CREATE UNIQUE INDEX IX_UniqueIndex ON Test_DB.dbo.Jobs(SalesOrderNumber)
As per your comment if you cannot create a unique index you may want to try the following SQL although it could cause too much locking and be affected by race conditions:
IF NOT EXISTS (SELECT 1 FROM Test_DB.dbo.Jobs j INNER JOIN Test_DB.dbo.SOTable so ON j.SalesOrderNumber = so.SPOrderReturnId)
BEGIN
UPDATE Test_DB.dbo.jobs
SET SalesOrderNumber = so.SOPOrderReturnID
FROM
Test_DB.dbo.Jobs j
INNER JOIN Test_DB.dbo.SOTable so ON j.SalesOrderNumber = so.SPOrderReturnId
Where
Reference = #Reference
END
The risk of this are that you are running to separate queries (the select and the update) so between them the state of the database could change. So it may be possible that the first query returns nothing exists for that Id but at the moment of the update other user has inserted/updated that data so the previous result is no longer true.
You can try to avoid this problem by using a isolation level that locks the table on the read (like Serializable) but that could cause locks and even deadlocks in the database.
The best solution here is the unique index. If a certain column has to be unique inside a table the best controller system is the db itself by defining constraints.
I need help creating this trigger, any help will be appreciate it, I can't save this script, it's saying 'Invalid column name OfferId'. But I checked all the columns names and they are correct
CREATE TRIGGER dbo.OrderOffer_UpdatedUnits
ON dbo.OrderOffer
FOR UPDATE
AS
BEGIN
DECLARE #OfferId char(5) SET #OfferId = (Select OfferId From INSERTED i, OrderOffer a Where i.OrderOfferid = a.OrderOfferid);
DECLARE #UnitsAvailable int SET #UnitsAvailable = (Select SUM(UnitsAvailable) From dbo.Offer Where OfferId=#OfferId);
UPDATE dbo.OrderOffer SET dbo.UnitsAvailable = #UnitsAvailable
FROM INSERTED i, OrderOffer a
WHERE i.OrderOfferid = a.OrderOfferid
END
Try this:
CREATE TRIGGER dbo.OrderOffer_UpdatedUnits
ON dbo.OrderOffer
FOR UPDATE
AS
BEGIN
UPDATE a
SET dbo.UnitsAvailable =
(
Select SUM(b.UnitsAvailable)
From dbo.Offer b
Where b.OfferId = a.OfferId
)
FROM OrderOffer a
INNER JOIN INSERTED i
ON i.OrderOfferid = a.OrderOfferid
END
NB: I removed the variables since that would assume you only updated one row at a time. Even if the update runs over multiple rows, the above should still work.
Ok. I am quite new to SQL triggers and have had some issues with them. Insert trigger works just fine, and the Delete trigger also. At first, doing a delete on multiple rows would only delete one, but I managed to figure that one out for myself :)
However, even after extensive searching (here and on Google) I am unable to find a satisfactory answer to the UPDATE trigger that I have. If I do an update like
UPDATE Customers Set CustomerUser = 0 Where CustomerStatus = 3
Then unfortunately, only the one record would be updated, and the other would remain as they were. Obviously, this is no good.
The trigger I am using is:
ALTER TRIGGER [dbo].[TRG_TriggerName] ON [dbo].[USER_Customers]
FOR UPDATE
AS
declare #customerid int;
declare #customervenue int;
declare #customeruser int;
declare #customerarea int;
declare #customerevent int;
declare #customerproject int;
declare #customerstatus int;
select #customerid=i.CustomerID from inserted i;
select #customervenue=i.CustomerVenue from inserted i;
select #customerarea=i.CustomerArea from inserted i;
select #customerevent=i.CustomerEvent from inserted i;
select #customerproject=i.CustomerProject from inserted i;
select #customeruser=i.CustomerUser from inserted i;
select #customerstatus=i.CustomerStatus from inserted i;
Update USER_Instances Set InstanceArea = #customerarea, InstanceVenue = #customervenue, InstanceUser = #customeruser, InstanceStatus = #customerstatus, InstanceEvent = #customerevent, InstanceLastUpdate = GetDate() Where InstanceObject = 17 AND InstanceIdentity = #customerid
GO
As you will immediately realize, this trigger is great - if you want to update just one record. Otherwise, it fails. Now - the main question here would be - How do I catch all the records that need updating, and update them all in one trigger action.
The examples I have seen here on Stack Overflow confuse me somewhat, or seem ineffective - for instance, it seems most of them deal with updating just ONE value in a second/other table, and not a whole bunch like I am trying to do. The ones that appear to work on multiple values, I can not understand :(
So after about 2 hours of searches, I give up, and hope that you can help me :) I realize this is a trigger-newbie issue, and though I know my MS-SQL, triggers are something I have never used, until now. So any help is greatly welcome :)
W
It seems that you need something like this
ALTER TRIGGER [dbo].[TRG_TriggerName] ON [dbo].[USER_Customers]
FOR UPDATE
AS
UPDATE USER_Instances
SET InstanceArea = i.CustomerArea,
InstanceVenue = i.CustomerVenue,
InstanceUser = i.CustomerUser,
InstanceStatus = i.CustomerStatus,
InstanceEvent = i.CustomerEvent,
InstanceLastUpdate = GetDate()
FROM USER_Instances JOIN inserted i
ON InstanceIdentity = i.CustomerID AND InstanceObject = 17
Since inserted virtual table can contain multiple rows you need to JOIN it to correctly do your UPDATE.
I am trying to update a row inside a cursor. What I am trying to do is update a chain of records with OLD_QTY and NEW_QTY. However when I try to do my update it gives the error The cursor is READ ONLY even though I included for update of OLD_QTY, NEW_QTY in my declration. It makes no difference if I include OLD_QTY and NEW_QTY in the select statement.
declare #current_inv_guid uniqueidentifier
declare #last_inv_guid uniqueidentifier
declare #current_vid int
declare #last_vid int
--declare #current_new_qty money
declare #last_new_qty money
--declare #current_old_qty money
declare iaCursor cursor
for select INV_GUID, old_VID
--, OLD_QTY, NEW_QTY
from #IA
order by INV_GUID, old_vid, ENTRY_NUM
for update --of OLD_QTY, NEW_QTY
open iaCursor
Fetch next from iaCursor into #current_inv_guid, #current_vid --, #current_old_qty, #current_new_qty
while ##fetch_status = 0
begin
--test to see if we hit a new chain.
if(#last_inv_guid <> #current_inv_guid or #current_vid <> #last_vid)
begin
set #last_new_QTY = (select #lots.QTY_RECEIVED from #lots where #lots.INV_GUID = #current_inv_guid and LOT_VID = #current_vid)
set #last_inv_guid = #current_inv_guid
set #last_vid = #current_vid
end
--update the current link in the chain
update #ia
set OLD_QTY = #last_new_QTY,
NEW_QTY = #last_new_QTY + QTY_CHANGE,
#last_new_QTY = #last_new_QTY + QTY_CHANGE
where current of iaCursor
--get the next link
fetch next from iaCursor into #current_inv_guid, #current_vid --, #current_old_qty, #current_new_qty
end
close iaCursor
deallocate iaCursor
Putting a order by in the select made the cursor read only.
You are not explicitly saying what behaviour you want, therefore, default rules apply, according to which, the cursor may or may not be updatable, depending on the underlying query.
It's perfectly fine to use order by in an updatable cursor, but you have to be more verbose and tell SQL Server what you want in details, for instance:
declare iaCursor cursor
local
forward_only
keyset
scroll_locks
for
select INV_GUID, old_VID
from #IA
order by INV_GUID, old_vid, ENTRY_NUM
for update of OLD_QTY, NEW_QTY
There's an import but subtle note on the documentation page that Patrick listed:
If the query references at least one table without a unique index, the
keyset cursor is converted to a static cursor.
And of course STATIC cursors are read-only.
Besides the reason you mentioned in your answer, what you're attmepting to do runs counter to the way SQL is meant to be used. Try to update the data in sets, not by rows.
I'm not positive, as I don't know your table design, but I believe the following should work. You may get better performance out of this. In particular, I'm assuming that QTY_CHANGE is coming from #ia, although this may not be the case.
UPDATE #ia as a set (OLD_QTY, NEW_QTY) = (SELECT #lots.QTY_RECEIVED + (COUNT(b.*) * a.QTY_CHANGE),
#lots.QTY_RECEIVED + ((COUNT(b.*) + 1) * a.QTY_CHANGE)
FROM #lots
LEFT JOIN #ia as b
ON b.INV_GUID = a.INV_GUID
AND b.OLD_VID = a.OLD_VID
AND b.ENTRY_NUM < a.ENTRY_NUM
WHERE #lots.INV_GUID = a.INV_GUID
AND #lots.LOT_VID = a.OLD_VID)
WHERE EXISTS (SELECT '1'
FROM #lots
WHERE #lots.INV_GUID = a.INV_GUID
AND #lots.LOT_VID = a.OLD_VID)
EDIT:
... the previous version of the answer was written with a DB2 perspective, although it would otherwise be db-agnostic. It also had the problem of using the same value of QTY_CHANGE for every row, which is unlikely. This should be a more idiomatic SQL Server 2008 version, as well as being more likely to output the correct answer:
WITH RT AS (SELECT #IA.inv_guid, #IA.old_vid, #IA.entry_num,
COALESCE(MAX(#Lots.qty_received), 0) +
SUM(#IA.qty_change) OVER(PARTITION BY #IA.inv_guid, #IA.old_vid
ORDER BY #IA.entry_num)
AS running_total
FROM #IA
LEFT JOIN #Lots
ON #Lots.inv_guid = #IA.inv_guid
AND #Lots.lot_vid = #IA.old_vid)
UPDATE #IA
SET #IA.old_qty = RT.running_total - #IA.qty_change, #IA.new_qty = RT.running_total
FROM #IA
JOIN RT
ON RT.inv_guid = #IA.inv_guid
AND RT.old_vid = #IA.old_vid
AND RT.entry_num = #IA.entry_num
Some cursor declarations do not allow updates. The documentation gives a hint in the following remark:
If the SELECT statement does not support updates (insufficient permissions, accessing remote tables that do not support updates, and
so on), the cursor is READ_ONLY.
I ran into the same issue when trying to join the "inserted" object of a trigger in the select statement of the cursor declaration.
Use the DYNAMIC clause, found in documentation.
Defines a cursor that reflects all data changes made to the rows in its result set as you scroll around the cursor. The data values, order, and membership of the rows can change on each fetch.