Help replace this SQL cursor with better code - sql

Can anyone give me a hand improving the performance of this cursor logic from SQL 2000. It runs great in SQl2005 and SQL2008, but takes at least 20 minutes to run in SQL 2000. BTW, I would never choose to use a cursor, and I didn't write this code, just trying to get it to run faster. Upgrading this client to 2005/2008 is not an option in the immediate future.
-------------------------------------------------------------------------------
------- Rollup totals in the chart of accounts hierarchy
-------------------------------------------------------------------------------
DECLARE #B_SubTotalAccountID int, #B_Debits money, #B_Credits money, #B_YTDDebits money, #B_YTDCredits money
DECLARE Bal CURSOR FAST_FORWARD FOR
SELECT SubTotalAccountID, Debits, Credits, YTDDebits, YTDCredits FROM xxx
WHERE AccountType = 0 AND SubTotalAccountID Is Not Null and (abs(credits)+abs(debits)+abs(ytdcredits)+abs(ytddebits)<>0)
OPEN Bal
FETCH NEXT FROM Bal INTO #B_SubTotalAccountID, #B_Debits, #B_Credits, #B_YTDDebits, #B_YTDCredits
--For Each Active Account
WHILE ##FETCH_STATUS = 0
BEGIN
--Loop Until end of subtotal chain is reached
WHILE #B_SubTotalAccountID Is Not Null
BEGIN
UPDATE xxx2
SET Debits = Debits + #B_Debits,
Credits = Credits + #B_Credits,
YTDDebits = YTDDebits + #B_YTDDebits,
YTDCredits = YTDCredits + #B_YTDCredits
WHERE GLAccountID = #B_SubTotalAccountID
SET #B_SubTotalAccountID = (SELECT SubTotalAccountID FROM xxx2 WHERE GLAccountID = #B_SubTotalAccountID)
END
FETCH NEXT FROM Bal INTO #B_SubTotalAccountID, #B_Debits, #B_Credits, #B_YTDDebits, #B_YTDCredits
END
CLOSE Bal
DEALLOCATE Bal

Update xx2
Set Credits = Credits + X1.CreditTotal
, Debits = Debits + X1.DebitTotal
, YtdDebits = YtdDebits + X1.YtdDebitTotal
, YtdCredits = YtdCredits + X1.YtdDebitTotal
From xx2 As X2
Join (
Select SubTotalAccountID, Sum(Debits) As DebitTotal, Sum(Credits) As CreditTotal
, Sum(YtdDebits) As YtdDebitTotal, Sum(YtdCredits) As YtdCreditTotal
From xxx
Where AccountType = 0
And SubTotalAccountID Is Not Null
And (
Credits <> 0
Or Debits <> 0
Or YtdCredits <> 0
Or YtdDebits <> 0
)
Group By SubTotalAccountID
) As X1
On X1.SubTotalAccountID = X2.GLAccountID
Without schema, I could not tell if the xxx table would return multiple rows for a given SubTotalAccountId. I assumed that it could and grouped the values by this column so that I get one row per SubTotalAccountId.
I also replaced your use of ABS in the WHERE clause with simply checks against zero. This should be substantially faster.
This UPDATE statement should be a complete replacement for your cursor.

A couple of suggestions:
1 - use the profiler to tell you which part of it runs slowly - you can get a duration for each statement
2 - run the initial select statement (the cursor declaration) outside of the procedure and check the query plan. Does it run quickly? Is it using indexes properly?
3 - same thing with the update statement - check the query plan and index usage
4 - the 'set' statement after the update looks odd - it seems to be getting a value into #B_SubTotalAccountID which is then replaced immediately by the 'fetch next'

Related

SQL - Running of SP sums the first row as NULL

I'm trying to develop a SP for Transaction operation , The SP gets as parameters the Username of the player , The Transaction amount, and the Type of the Transaction if it's Deposit / Draw .
My problem is when the first row enters to the table my TotalAmount column in the tables the sums the total amount by Username starts from NULL for each username instead of the first deposit .
From the second row to the same username it's sums the total amount fine .
The SP -
https://i.stack.imgur.com/8RmW2.png
The Problem -
https://i.stack.imgur.com/NYTSx.png
Thanks!
You have code like this :
SET #TotalAMT = (#TransactionAMT + ( ... some code here))
Change it like this
SET #TotalAMT = (#TransactionAMT + COALESCE( ... some code here), 0)

Improving SQL Query to check condition

Currently, the sql code that I have is to check for the three fee_code records do any of them have election_code = NA.
I'm trying to figure out how to change the code to check that all of the 3 records have election_code = NA.
Original Query:
DECLARE #fee_code1 tinyint = 1
,#fee_code2 tinyint = 2
,#fee_code3 tinyint = 3
,#election_code tinyint = 0
IF EXISTS(SELECT *
FROM Product
WHERE
product_id = #product_id
AND fee_code in (#fee_code1, #fee_code2, #fee_code3)
AND election_code = #election_code)
The solution I have is to do a count check, but I know count has a performance impact and there's probably a better way to do it. What's a better way to do it?
Count solution:
IF ((SELECT COUNT(*)
FROM Product
WHERE
product_id = #product_id
AND fee_code in (#fee_code1, #fee_code2, #fee_code3)
AND election_code = #election_code) = 3)

Issue with While Loop in SQL function

I'm writing a function that should add each line item quantity multiplied by its unit cost and then iterating through the entire pickticket (PT). I don't get an error when altering the function in SQL Server or when running it, but it gives me a 0 as the output each time.
Here is an example:
[PT 1]
[Line 1 - QTY: 10 Unit Cost: $5.00] total should be = $50.00
[Line 2 - QTY: 5 Unit Cost: $2.50] total should be = $12.50
The function should output - $62.50
Not really sure what I'm missing here, but would appreciate the help.
Alter Function fn_CalculateAllocatedPTPrice
(#psPickTicket TPickTicketNo)
-------------------------------
Returns TInteger
As
Begin
Declare
#iReturn TInteger,
#iTotalLineNumbers TInteger,
#iIndex TInteger,
#fTotalCost TFloat;
set #iIndex = 1;
set #iTotalLineNumbers = (ISNULL((select top 1 PickLineNo
from tblPickTicketDtl
where PickTicketNo = #psPickTicket
order by PickLineNo desc), 0)) /* This returns the highest line number */
while(#iIndex <= #iTotalLineNumbers)
BEGIN
/* This should be adding up the total cost of each line item on the PT */
set #fTotalCost = #fTotalCost + (ISNULL((select SUM(P.RetailUnitPrice*P.UnitsOrdered)
from tblPickTicketDtl P
left outer join tblCase C on (P.PickTicketNo = C.PickTicketNo)
where P.PickTicketNo = #psPickTcket
and P.PickLineNo = #iIndex
and C.CaseStatus in ('A','G','K','E','L','S')), 0))
set #iIndex = #iIndex + 1;
END
set #iReturn = #fTotalCost;
_Return:
Return(#iReturn);
End /* fn_CalculateAllocatedPTPrice */
It seems simple aggregation should suffice
A few points to note:
WHILE loops and cursors are very rarely needed in SQL. You should stick to set-based solutions, and if you find yourself writing a loop you shuold question your code from its beginnings.
Scalar functions are slow and inefficient. Use an inline Table function, which you can correlate with your main query either with an APPLY or a subquery
Your left join becomes an inner join because of the where predicate
User defined types are not normally a good idea (when they are just aliasing system types)
CREATE OR ALTER FUNCTION fn_CalculateAllocatedPTPrice
(#psPickTicket TPickTicketNo)
RETURNS TABLE AS RETURN
SELECT fTotalCost = ISNULL((
SELECT SUM(P.RetailUnitPrice * P.UnitsOrdered)
from tblPickTicketDtl P
join tblCase C on (P.PickTicketNo = C.PickTicketNo)
where P.PickTicketNo = #psPickTcket
and C.CaseStatus in ('A','G','K','E','L','S')
), 0);
GO

how to fix my control number problem im using trigger

Good day every one this is my trigger code that after the user will insert a data and select a pr date it will generate a control number in the database but my problem here is when the user pick a pr_date that is 01-28-2021 and after adding record it display a value in the datatabase 202103-1 it must display a value of 202101-1
BEGIN
SET #v1 = (SELECT control_number FROM tbl_worklog where DATE_FORMAT(pr_date,'%Y%m') =DATE_FORMAT(NOW(),'%Y%m') ORDER BY id DESC LIMIT 1);
IF (#v1 is null) THEN
SET new.control_number = (CONCAT(CAST(DATE_FORMAT(NOW(),'%Y%m') as CHAR),"-1"));
ELSE
SET #v2 = (CAST(SUBSTRING_INDEX(#v1,"-",-1) as int)+1);
SET new.control_number = (CONCAT(CAST(DATE_FORMAT(NOW(),'%Y%m') as CHAR),"-",CAST(#v2 as char)));
END IF;
END
To determine the control number, you're using the formula
DATE_FORMAT(NOW(), '%Y%m')
which will look at the current date, which is why you're getting 202103 (it's currently March, the third month). You should instead use
DATE_FORMAT(pr_date, '%Y%m')

i want to optimize this query

i have the following tables :
dbo.Details
Name Type SubType SerialNumber
D_01 TxA STxA1 4
D_02 TxB STxB2 3
D_03 TxC STxC1 2
D_04 TxD STxD1 7
D_05 TxD STxD1 1
D_06 TxD STxD1 9
dbo.DetailsType
Code Name
TxA A
TxB B
TxC C
...
dbo.DetailsSubType
Code Type Name CustomOR
STxA1 TxA A1 1
STxA2 TxA A2 0
STxB1 TxB B1 1
STxB2 TxB B2 0
STxC1 TxC C1 1
STxC2 TxC C2 0
STxD TxD D1 1
I want to know what query (A or B) is optimal in your opinion, with explanation please:
QUERY A
CREATE PROCEDURE XXX
(
#type nvarchar(10),
#subType nvarchar(10) = null
)
AS
BEGIN
declare #custom bit = 0;
if (#subType is not null)
begin
select #custom = CustomOR from dbo.DetailsSubType where SubType = #subType
end
select
DTST.SubType,
DT.SerialNumber
from dbo.Details as DT
left join DetailsSubType as DTST
on DT.SubType = DTST.Code
where
DT.Type = #type
and
(
#subType is null or
(#custom = 0 and DTST.CustomOR= 0) or
(#custom = 1 and DT.SubType = #subType)
)
END
QUERY B
declare #custom bit = 0;
if (#subType is not null)
begin
select #custom = CustomOR from dbo.DetailsSubType where SubType = #subType
end
if (#custom = 0)
begin
select
DTST.SubType,
DT.SerialNumber
from dbo.Details as DT
left join DetailsSubType as DTST
on DT.SubType = DTST.Code
where
DT.Type = #type
and
DTST.CustomOR = 0
end
else
begin
select
DTST.SubType,
DT.SerialNumber
from dbo.Details as DT
left join DetailsSubType as DTST
on DT.SubType = DTST.Code
where
DT.Type = #type
and
(DTST.CustomOR = 1 and DT.SubType = #subType)
end
Unfortunately, neither may be optimal. I am guessing that your concern is related to performance and the execution plan of the query. The second method definitely gives SQL Server better opportunities for the optimization plan -- simply because OR is really hard to optimize.
But this doesn't take into account "parameter sniffing". There are lots of articles about this subject (here is a reasonable one).
Parameter sniffing means that SQL Server compiles the query the first time the stored procedure is called. This saves the overhead of recompiling the queries -- a savings that is important if you have lots of "small" queries. But a fool's bargain for larger queries -- because it does not take statistics on the table into account.
I would suggest that you look into articles about this. You may find that the second solution is sufficient. You may find that simply adding option recompile is sufficient. You may find that you want to construct the query as dynamic SQL -- hey, you know it will be recompiled anyway. But you'll be able to make a more informed decision.
You could consider writing three queries that partition your results, where each query handles exactly one of your OR predicates, and UNION ALL the results.
In pseudo code:
SELECT ... FROM ... WHERE #subType is null
UNION ALL
SELECT ... FROM ... WHERE #subType is NOT null AND DTST.CustomOR = 0 AND #custom = 0
UNION ALL
SELECT ... FROM ... WHERE #subType is NOT null AND DT.SubType = #subType AND #custom = 1
Having said that, what I actually think is that you should change your data model. It is extremely had (and very slow) to move forward with this setup. You probably haven't normalized your database properly.