Microsoft SQL Server uses outdated values in stored procedures - sql

I have a SQL Stored Procedure which is never ending because of some values which are cached. But that is just a guess after debugging the procedure.
while #poid is not NULL
BEGIN
Update Item set Sales = (Select Sales from V_ITEM_Hierarchy where POID=#poid) where ItemID=#poid
Select #poid = i.ItemID
from V_ITEM_Hierarchy t inner join Item i on (t.POID = i.POID)
where ( abs(coalesce(t.Sales,0)-coalesce(i.Sales,0)) > 0.0001
END
I update the value "Sales" in Table Item with the Sales value of a view called "V_ITEM_Hierarchy" and then look again for values which are different. When I debug through the procedure the select-statement always returns the same value even if the Sales values are not different anymore because they were updated.
I tried to insert the command "DBCC DROPCLEANBUFFERS" but the select-statement still returns old values.

If the second query does not return any rows the value of #poid will NOT be updated. What you need is
while #poid is not NULL
BEGIN
Update Item set Sales = (Select Sales from V_ITEM_Hierarchy where POID=#poid) where ItemID=#poid
set #poid = null
Select #poid = i.ItemID
from V_ITEM_Hierarchy t inner join Item i on (t.POID = i.POID)
where ( abs(coalesce(t.Sales,0)-coalesce(i.Sales,0)) > 0.0001
END

I think you would be much better off changing this to a cursor because you will always have the possibility of an endless loop the way it is written now.
DECLARE #poid INT
DECLARE item_cursor CURSOR FOR
Select i.ItemID
from V_ITEM_Hierarchy t inner join Item i on (t.POID = i.POID)
where ( abs(coalesce(t.Sales,0)-coalesce(i.Sales,0)) > 0.0001
OPEN item_cursor
FETCH NEXT FROM item_cursor INTO #poid
WHILE ##FETCH_STATUS = 0
BEGIN
Update Item set Sales = (Select Sales from V_ITEM_Hierarchy where POID=#poid) where ItemID=#poid
FETCH NEXT FROM item_cursor INTO #poid
END
DEALLOCATE item_cursor

Related

Make trigger with variables work for multiple row inserts

I have a warehouse database and want to implement a trigger which makes sure that the number of products returned by customers cannot exceed the number of products sold minus the number of products returned previously.
It works for one insert in the RETURNS table, but gives me an error if I insert multiple rows at once.
How can I fix that?
Thanks!
The tables affected are
SALES_ITEMS (PRODUCT, SALE, SALES_QUANTITY)
RETURNS (IDRETURN, PRODUCT, SALE, RETURN_QUANTITY)
CREATE TRIGGER tr
ON Returns
AFTER UPDATE, INSERT
AS
BEGIN
DECLARE #product INTEGER;
DECLARE #sale INTEGER;
SET #product = (SELECT PRODUCT FROM Inserted);
SET #sale = (SELECT SALE FROM Inserted);
IF (SELECT SUM(r.RETURN_QUANTITY)
FROM RETURNS r
WHERE r.PRODUCT = #product
AND r.SALE = #sale) > (SELECT s.SALES_QUANTITY
FROM SALE_ITEMS s
WHERE s.PRODUCT = #product AND s.SALE = #sale)
BEGIN
ROLLBACK TRANSACTION;
END
END
Hmmm . . . this is a bit complicated. One method is to join the returns and sales together to see if there are any cases where the returns exceed the sales:
if exists (select 1
from RETURNS r join
inserted i
on r.PRODUCT = i.product and r.SALE = i.sale join
sales_items si
on si.product = i.product and si.sale = i.sale
group by r.product, s.sales_quantity
having sum(r.return_quantity) > s.sales_quantity
)
begin
ROLLBACK TRANSACTION;
end;

sql stored procedureExecution Timeout Expired.

i have couple of stored procedures in our sql server 2008 that gets executed one after another, im facing a sql error (Execution Timeout Expired. The timeout period elapsed prior to completion of the operation or the server is not responding) at the last one which updates or inserts against a 1 million records table while it works with smaller files but when updating or inserting this number of records it stops and throws that error , how can i tune the stored procedure, i tried to clean the server with sp_updatestates but no luck, i'd appreciate any help, thank you, here is my stored procedure code :
USE [DBTest]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[SP_UpdateData]
#ItemsExt dbo.TT_ItemsExt READONLY
AS
BEGIN
SET NOCOUNT ON;
Declare
#ItemID nvarchar(36)
,#DescE nvarchar(50)
,etc
,etc
---------------------------------------
DECLARE Item_cursor CURSOR FOR
SELECT
ItemID
,DescE
,etc
,etc
FROM #ItemsExt
OPEN Item_cursor
FETCH NEXT FROM Item_cursor INTO
#ItemID
,#DescE
,etc
,etc
WHILE ##FETCH_STATUS = 0
BEGIN
If exists (select * from Items WHERE ItemID = #ItemID)
Begin
Update dbo.Items SET
DescEng = #DescE
,etc = #etc
WHERE ItemID = #ItemID
End
ElSE
Begin
Declare #ItemNumber nvarChar(36)
SET #ItemNumber = REPLACE((REPLACE(RIGHT(#ItemID, LEN(#ItemID) - 1))
insert into dbo.Items
(ItemID , etc)
VALUES (#ItemID, etc)
End
--ItemsReplacment Table
If exists (select * from dbo.ItemsReplacement WHERE ItemID = #ItemID)
Begin
Update dbo.ItemsReplacement SET
ItemAlter= #AlternativeItem
,etc
WHERE ItemID = #ItemID
End
ElSE
Begin
Insert Into dbo.ItemsReplacement Values (
#ItemID
,etc
,etc)
End
FETCH NEXT FROM Item_cursor INTO
#ItemID
,#DescE
,etc
,etc
END
CLOSE Item_cursor;
DEALLOCATE Item_cursor;
END
ps: the timeout of execution is set to 0 meaning infinite.
Here's a better aproach to those updates and inserts:
ALTER PROCEDURE [dbo].[SP_UpdateData]
#ItemsExt dbo.TT_ItemsExt READONLY
AS
BEGIN
UPDATE IT
SET
DescEng = ITE.DescE,
etc = ITE.etc
FROM
dbo.Items IT
INNER JOIN ItemsExt ITE ON (IT.ItemID = ITE.ItemID);
INSERT INTO
dbo.Items
(
ItemID,
etc
)
SELECT
ITE.ItemID,
ITE.etc
FROM
dbo.ItemsExt ITE;
WHERE
NOT EXISTS (SELECT 1 FROM dbo.Items WHERE ItemID = ITE.ItemID);
UPDATE ITR
SET
ItemAlt = 0, --AlternativeItem?
etc = ITE.etc
FROM
dbo.ItemsReplacement ITR
INNER JOIN ItemsExt ITE ON (ITR.ItemID = ITE.ItemID);
INSERT INTO
dbo.ItemsReplacement
(
ItemID,
etc
)
SELECT
ITE.ItemID,
ITE.etc
FROM
dbo.ItemsExt ITE;
WHERE
NOT EXISTS (SELECT 1 FROM dbo.ItemsReplacement WHERE ItemID = ITE.ItemID);
END
That way you avoid unecessary cursor-loops and let the Database do it's magic on planning the execution.
And by the way: Shouldn't it have any kind of "restriction" on those updates and inserts? Are you planning on run all those tables on all executions?
!! On your original code you were using #AlternativeItem that I couldn't find anywhere. Used 0 in my response.

after trigger update statement take long time

I created a trigger as below
alter trigger sale_Trigger_Update on sale
after update
as
begin
Declare #old_value varchar(50)
Declare #new_value varchar(50)
Declare #sale_id UNIQUEIDENTIFIER
DECLARE new_cur CURSOR FORWARD_ONLY READ_ONLY LOCAL FOR
SELECT saleid
FROM INSERTED
open new_cur
Fetch Next from new_cur into #sale_id
while ##FETCH_STATUS = 0
Begin
set #old_value = (select enddate from deleted where SaleID = #sale_id)
set #new_value = (select enddate from inserted where SaleID = #sale_id)
insert into zzz (old_value,new_value) values(#old_value,#new_value)
end
CLOSE new_cur
DEALLOCATE new_cur
end
Then I do an update statement as below
update sale
set enddate = null
Sale table contain only 2 rows
and the execution is continuing unlimited.
I tried
update sale
set enddate = null
where saleid = 10
same problem.
Then I forcefully stopped the execution. Then checked the sale table and zzz table. No changes happened.
I am sure there is some issue in cursor. Can somebody show some light on it.
****Edited****
Actually I need to check enddate in deleted is null and enddate in inserted is not null
open new_cur
Fetch Next from new_cur into #sale_id
while ##FETCH_STATUS = 0
Begin
set #old_value = (select enddate from deleted where SaleID = #sale_id)
set #new_value = (select enddate from inserted where SaleID = #sale_id)
if #old_value = null and #new_value != null
begin
SELECT approval.*,
(select diag.*
from diag diag
where approval.id =diag.id
FOR XML PATH('diag'), TYPE
),
(select ser.*
from ser ser
where approval.id =ser.id
FOR XML PATH('ser'), TYPE
)
FROM approval approval,
where approval.id = 1
and approval.saleid =#saleid
FOR XML PATH, ELEMENTS,
root('Head')
end if
end
CLOSE new_cur
DEALLOCATE new_cur
Regarding the cursor.
Could you replace your trigger with?
alter trigger sale_Trigger_Update on sale
after update
as
begin
insert into zzz (old_value,new_value)
select
--i.SalesID,
d.enddate,
i.enddate
from inserted i
inner join deleted d on
i.SaleID = d.SaleID
where
d.enddate is null and
i.enddate is not null
end

How can I improve this query for performance, provide status, and run as SQL job?

I have two tables, in two different databases. I am using one of the tables to update values in the other database table.
There are over 200,000 rows to iterate through, and it is taking several hours to run, on an Amazon c3.xlarge instance.
Below is the query I am running, and I am wondering three things:
Can this query be optimized to perform faster?
I would like to add a count to get the number of actual records
updated.How?
How can I turn this into a SQL job?
DECLARE #id VARCHAR(12) -- unique id
DECLARE #currentval VARCHAR(64) -- current value
DECLARE #newval VARCHAR(64) -- updated value
DECLARE db_cursor1 CURSOR FOR
SELECT b.[id], a.status, b.[New Status]
FROM db1.dbo.['account'] as b inner join db2.dbo.accounttemp as a on a.ACCOUNTID = b.[ID]
OPEN db_cursor1
FETCH NEXT FROM db_cursor1
INTO #id,
#currentval,
#newval
WHILE ##FETCH_STATUS = 0
BEGIN
UPDATE db2.dbo.accounttemp
SET status = #newval
WHERE ACCOUNTID = #id
AND STATUS = #currentval
FETCH NEXT FROM db_cursor1
INTO #id,
#currentval,
#newval
END
CLOSE db_cursor1
DEALLOCATE db_cursor1
By reviewing the procedure, you will see that you can completely remove the cursor using the following SQL
UPDATE db2.dbo.accounttemp
SET status = a.Status
FROM db2.dbo.accounttemp at
INNER JOIN db1.dbo.['account'] AS a ON a.Id = at.[ACCOUNTID]
WHERE a.Status = at.Status
call the following line to return the rows affected by the update
RETURN ##ROWCOUNT
You can create an SQL maintenance plan to run this on scheduled basis

How to update a column fetched by a cursor in TSQL

Before I go any further: Yes, I know that cursors perform poorly compared with set-based operations. In this particular case I'm running a cursor on a temporary table of 100 or so records, and that temporary table will always be fairly small, so performance is less crucial than flexibility.
My difficulty is that I'm having trouble finding an example of how to update a column fetched by a cursor. Previously when I've used cursors I've retrieved values into variables, then run an update query at each step based upon these values. On this occasion I want to update a field in the temporary table, yet I can't figure out how to do it.
In the example below, I'm trying to update the field CurrentPOs in temporary table #t1, based upon a query that uses #t1.Product_ID to look up the required value. You will see in the code that I have attempted to use the notation curPO.Product_ID to reference this, but it doesn't work. I have also attempted to use an update statement against curPO, also unsuccessfully.
I can make the code work by fetching to variables, but I'd like to know how to update the field directly.
I think I'm probably missing something obvious, but can anyone help?
declare curPO cursor
for select Product_ID, CurrentPOs from #t1
for update of CurrentPOs
open curPO
fetch next from curPO
while ##fetch_status = 0
begin
select OrderQuantity = <calculation>,
ReceiveQuantity = <calculation>
into #POs
from PurchaseOrderLine POL
inner join SupplierAddress SA ON POL.Supplier_ID = SA.Supplier_ID
inner join PurchaseOrderHeader POH ON POH.PurchaseOrder_ID = POL.PurchaseOrder_ID
where Product_ID = curPO.Product_ID
and SA.AddressType = '1801'
update curPO set CurrentPOs = (select sum(OrderQuantity) - sum(ReceiveQuantity) from #POs)
drop table #POs
fetch next from curPO
end
close curPO
deallocate curPO
After doing a bit more googling, I found a partial solution. The update code is as follows:
UPDATE #T1
SET CURRENTPOS = (SELECT SUM(ORDERQUANTITY) - SUM(RECEIVEQUANTITY)
FROM #POS)
WHERE CURRENT OF CURPO
I still had to use FETCH INTO, however, to retrieve #t1.Product_ID and run the query that produces #POs, so I'd still like to know if it's possible to use FETCH on it's own.
Is this what you want?
declare curPO cursor
for select Product_ID, CurrentPOs from #t1
for update of CurrentPOs
open curPO
fetch next from curPO
while ##fetch_status = 0
begin
update curPO set CurrentPOs =
(select sum(<OrderQuantityCalculation>)
from PurchaseOrderLine POL
inner join SupplierAddress SA ON POL.Supplier_ID = SA.Supplier_ID
inner join PurchaseOrderHeader POH ON POH.PurchaseOrder_ID = POL.PurchaseOrder_ID
where Product_ID = curPO.Product_ID
and SA.AddressType = '1801') -
(select sum(<ReceiveQuantityCalculation>)
from PurchaseOrderLine POL
inner join SupplierAddress SA ON POL.Supplier_ID = SA.Supplier_ID
inner join PurchaseOrderHeader POH ON POH.PurchaseOrder_ID = POL.PurchaseOrder_ID
where Product_ID = curPO.Product_ID
and SA.AddressType = '1801')
fetch next from curPO
end
close curPO
deallocate curPO
Maybe you need something like that:
update DataBaseName..TableName
set ColumnName = value
where current of your_cursor_name;
Here's an example to calculate one column based upon values from two others (note, this could be done during the original table select). This example can be copy / pasted into an SSMS query window to be run without the need for any editing.
DECLARE #cust_id INT = 2, #dynamic_val NVARCHAR(40), #val_a INT, #val_b INT
DECLARE #tbl_invoice table(Cust_ID INT, Cust_Fees INT, Cust_Tax INT)
INSERT #tbl_invoice ( Cust_ID, Cust_Fees, Cust_Tax ) SELECT 1, 111, 11
INSERT #tbl_invoice ( Cust_ID, Cust_Fees, Cust_Tax ) SELECT 2, 222, 22
INSERT #tbl_invoice ( Cust_ID, Cust_Fees, Cust_Tax ) SELECT 3, 333, 33
DECLARE #TblCust TABLE
(
Rec_ID INT
, Val_A INT
, Val_B INT
, Dynamic_Val NVARCHAR(40)
, PRIMARY KEY NONCLUSTERED (Rec_ID)
)
INSERT #TblCust(Rec_ID, Val_A, Val_B, Dynamic_Val)
SELECT Rec_ID = Cust_ID, Val_A = Cust_Fees, Val_B = Cust_Tax, NULL
FROM #tbl_invoice
DECLARE cursor_cust CURSOR FOR
SELECT Rec_ID, Val_A, Val_B, Dynamic_Val
FROM #TblCust
WHERE Rec_ID <> #cust_id
FOR UPDATE OF Dynamic_Val;
OPEN cursor_cust;
FETCH NEXT FROM cursor_cust INTO #cust_id, #val_a, #val_b, #dynamic_val;
WHILE ##FETCH_STATUS = 0
BEGIN
UPDATE #TblCust
SET Dynamic_Val = N'#c = "' + LTRIM(STR((#val_a + #val_b), 40)) + N'"'
WHERE CURRENT OF cursor_cust
FETCH NEXT FROM cursor_cust INTO #cust_id, #val_a, #val_b, #dynamic_val;
END
CLOSE cursor_cust
DEALLOCATE cursor_cust
SELECT * FROM #TblCust