My stored procedure is too slow - sql-server

My stored procedure is too slow. Can I make it faster?
ALTER PROCEDURE [dbo].[CalBuyAvg]
#companyCode INT
AS
BEGIN
BEGIN TRANSACTION CalBuyAvg
DELETE FROM dbo.tmpCalSood
WHERE CompanyCode = #companyCode
DECLARE #id INT
DECLARE #previd INT
DECLARE #prvBuyAvg DECIMAL(18,0)
DECLARE #prvKcode INT
DECLARE db_cursor CURSOR FOR
SELECT Id, LAG(Id) OVER (ORDER BY kalaCode, Tarikh, id) prevId
FROM dbo.CalMandeVW
ORDER BY kalaCode, Tarikh, id
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #id, #previd
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #prvKcode = KalaCode, #prvBuyAvg = buyAvg
FROM dbo.TmpCalSood
WHERE id = #previd
INSERT INTO dbo.TmpCalSood ([Id], [Tarikh], [KalaCode], [kalaType], [Varede], [fiVarede], [SumVarede],
SaleMali, AnbarCode, pType, pCode, ProjectCode, CompanyCode,
Sadere, fiSadere, SumSadere, Mandeh, LastMande , buyavg)
SELECT
[Id], [tarikh], [KalaCode], 0, [Varede], [fiVarede], [sumVarede],
SaleMali, AnbarCode, pType, pCode, ProjectCode, CompanyCode,
Sadere, fiSadere, SumSadere, Mandeh, lastMande,
CASE
WHEN lastMande = 0
THEN dbo.CalMandeVW.fiVarede
ELSE
CASE
WHEN varede = 0
THEN
CASE
WHEN #prvKcode = kalaCode
THEN #prvBuyAvg
ELSE 0
END
ELSE
CASE
WHEN #prvKcode = kalaCode
THEN ((sumVarede) + (LastMande*#prvBuyAvg)) / (varede+lastMande)
ELSE dbo.CalMandeVW.fiVarede
END
END
END AS newbuyAvg
FROM
dbo.CalMandeVW
WHERE
id = #id
FETCH NEXT FROM db_cursor INTO #id, #previd
END
CLOSE db_cursor
DEALLOCATE db_cursor;
COMMIT
RETURN 1
END

Cursors are performance killers.
Use
INSERT...INTO....SELECT
Syntax for better performance

Related

How can I display multiple ID's and value in single column SQL?

I have a 'Knowledge' table where I'm adding tags. I'm saving a many-to-many relationship to other tables. Let me show my table structure:
I want to store a comma delimited string in a single table of persons. For example:
Id
TagNames
28
Azure, SQL, WebConfig
29
Network
Thanks a lot of for answers.
I found that. I used cursor and I'm listing my tags in one column.
SELECT kn.ID,Title,ExplainProblem,SolutionDescription,kn.CreateUserID, M.Name +'
' + M.Surname as 'TechNameAndSurname',kn.CreateDate,IsPrivate,kn.IsActive,
case
when kn.IsPrivate=1 then 'Sadece Ben'
when kn.IsPrivate=0 then 'Seçili Gruplar'
else ''
end WhoIsShowing
into #temp FROM Knowledges kn
inner join Members M on M.ID = kn.CreateUserID where kn.IsActive=1
alter table #temp add TagsNames nvarchar(250)
DECLARE #ID int
DECLARE db_cursor CURSOR FOR SELECT Id FROM #temp
OPEN db_cursor FETCH NEXT FROM db_cursor INTO #ID
WHILE ##FETCH_STATUS = 0
BEGIN
IF OBJECT_ID('tempdb..#tags') IS NOT NULL DROP TABLE #tags
select tag.TagName,ktg.ID into #tags from KnowledgeTags ktg
inner join Tags tag on tag.ID = ktg.TagID
where ktg.KnowledgeID=#ID
order by ktg.TagID
--
DECLARE #tags nvarchar(250) = ''
DECLARE #kID int, #tagName nvarchar(250)
DECLARE db_cursor2 CURSOR FOR SELECT ID,TagName FROM #tags
OPEN db_cursor2 FETCH NEXT FROM db_cursor2 INTO #kID, #tagName
WHILE ##FETCH_STATUS = 0
BEGIN
SET #tags = #tags + ', ' + #tagName
FETCH NEXT FROM db_cursor2 INTO #kID, #tagName
END CLOSE db_cursor2 DEALLOCATE db_cursor2
--
update #temp set TagsNames = RIGHT(#tags, LEN(#tags)-1) where ID = #ID
FETCH NEXT FROM db_cursor INTO #ID
END CLOSE db_cursor DEALLOCATE db_cursor
SELECT * FROM #temp

Variables set in db_cursor1 cannot be used as where clause criteria in db_cursor2

I'm not able to next cursors and and have the variables set from db_cursor1 used in the where clause of db_cursor2. By the time db_cursor2 declared it only works with the settings for #curLocation and #curTimeBandName at the time of declaration. I'm hoping "FETCH FIRST FROM db_cursor1 INTO #curLocation, #curTimeBandName" will set these variables to new values and I can do a new fetch on db_cursor2 with these new critera, but it doesn't work. I hate to move the declaration and deallocation for db_cursor2 inside the loop. It there a better way to next these cursors where the variables set in #1 can be used as criteria in #2.
DECLARE
#Id INT,
#prevId INT = 0,
#prevEndDate DATETIME,
#curStartDate DATETIME,
#curEndDate DATETIME,
#curLocation NVARCHAR(100) = 'PHX',
#curTimeBandName NVARCHAR(50) = 'Long3'
SELECT #PrevEndDate = ISNULL(#startDate,'1900/01/01');
DECLARE db_cursor1 CURSOR
SCROLL
FOR
SELECT DISTINCT [Location], TimeBandName
FROM #Temp
ORDER BY Location, TimeBandName;
-- SELECT #curLocation = 'PHX', #curTimeBandName = 'Long3';
DECLARE db_cursor2 CURSOR
SCROLL
FOR
SELECT Id, StartDate, EndDate
FROM #Temp
WHERE [Location] = #curLocation
AND TimeBandName = #curTimeBandName
ORDER BY Location, TimeBandName, StartDate;
OPEN db_cursor1
OPEN db_cursor2
FETCH FIRST FROM db_cursor1 INTO #curLocation, #curTimeBandName
WHILE (##FETCH_STATUS = 0)
BEGIN
FETCH NEXT FROM db_cursor2 INTO #Id, #curStartDate, #curEndDate
WHILE (##FETCH_STATUS = 0)
BEGIN
IF (#prevEndDate + 1 < #curStartDate)
BEGIN
UPDATE t
SET t.BlackoutStartDate = #PrevEndDate + 1,
t.BlackoutEndDate = #CurStartDate -1
FROM #Temp t
WHERE t.Id = #Id
END
SELECT #prevId = #id, #prevEndDate = #curEndDate
FETCH NEXT FROM db_cursor2 INTO #Id, #curStartDate, #curEndDate
END
FETCH NEXT FROM db_cursor1 INTO #curLocation, #curTimeBandName
END
CLOSE db_cursor2;
DEALLOCATE db_cursor2;
CLOSE db_cursor1;
DEALLOCATE db_cursor1;
I found a better way. I dumped the second cursor all together. Instead I added a ProcessStatus field to the temp table to track the next unfinished record and added the below code to track witch of the next records to work on.
WHILE ...
BEGIN
SELECT TOP 1
#Id = Id,
#curStartDate = StartDate,
#curEndDate = #EndDate
FROM #Temp
WHERE [Location] = #curLocation
AND TimeBandName = #curTimeBandName
AND ProcessStatus = 0
ORDER BY Location, TimeBandName, StartDate;
.... do work ...
UPDATE t
SET ProcessStatus = 1
FROM #Temp t
WHERE Id = #Id;
END

Dynamic Cursor SQL Server

I have this global cursor that is created by a string like this, however when execute, I get this error message :
A cursor with the name 'crsDTO' does not exist.
Code:
DECLARE #Cursor NVARCHAR(MAX);
SET #Cursor = 'DECLARE crsDTO CURSOR FOR SELECT p.ID, p.Price, p.Count FROM Business.Products';
exec sp_executesql #Cursor;
OPEN crsDTO; -- fails here <<<<<<<<
BEGIN TRY
FETCH NEXT FROM crsDTO INTO #ID, #Price, #Count;
WHILE 0 = ##fetch_status
BEGIN
PRINT(#ID)
FETCH NEXT FROM crsDTO INTO #ID, #Price, #Count;
END;
CLOSE crsDTO;
DEALLOCATE crsDTO;
END TRY
BEGIN CATCH
CLOSE crsDTO;
DEALLOCATE crsDTO;
END CATCH
I looked around everything looks to be fine.. and I can't find why it's not working.
UPDATE
This SP is going to bulk update either price or stock or both. i might be wrong and there might be alternative way which is much better than this i am open to all correction.
However this cursor is going to be filtered based on user opinion. it can change stock/prices(as percentage amount or basic amount) based on the filters.
so for example user wants bulk change the prices for only specific brandId or combination of BrandId/CategoryId and SupplierId or none of them(which means every product).
CREATE procedure [Business].[Product_BulkUpdate]
(
#PO_Error int OUTPUT,
#PO_ErrorMessage Nvarchar(Max) OUTPUT,
#PO_Step int OUTPUT,
#CallerUserId uniqueidentifier,
#CategoryId uniqueidentifier = null,
#BrandId uniqueidentifier = null,
#SupplierId uniqueidentifier = null,
#ProductName nvarchar(max) = null,
#Amount float = null,
#AmountPercentage float = null,
#IsInStock bit = null
)
as
DECLARE #ID Uniqueidentifier;
DECLARE #Price int;
DECLARE #Count int;
DECLARE #KW nvarchar(max);
DECLARE #Cursor nvarchar(max);
DECLARE #WhereClause nvarchar(max);
set #WhereClause = ' 1=1 ';
if (#ProductName is not null)
set #WhereClause =#WhereClause + ' And p.Name like N'''+'%'+cast(#ProductName as nvarchar(4000))+'%'+''' ';
if (#CategoryId is not null)
set #WhereClause =#WhereClause + ' And c.ID in (SELECT cf.id FROM Business.GetCategoryChilds('''+CAST(#CategoryId as nvarchar(50)) +''') cf) ';
if(#SupplierId is not null)
set #WhereClause = #WhereClause + ' AND p.SupplierId in (' + CAST(#SupplierId as nvarchar(50)) + ') ';
IF(#BrandId is not null)
set #WhereClause = #WhereClause + ' AND bb.ID in (' + CAST(#BrandId as nvarchar(50)) + ')';
SET #Cursor = ' DECLARE crsDTO cursor for
SELECT p.ID, p.Price, p.Count FROM Business.Products p
INNER JOIN Kernel.BaseEntity b on b.ID = p.ID AND b.IsDelete = 0
LEFT JOIN Business.Brand bb on bb.ID = p.BrandId
LEFT JOIN Business.Category c on c.ID = p.CategoryId
LEFT JOIN MarketPlace.Supplier s on s.SupplierId = p.SupplierId
WHERE '+#WhereClause+' AND c.CategoryTypeId = 10700';
begin
--- Auto generated procedure
SET NOCOUNT ON;
SET #PO_Error = 0;
SET #PO_Step = 0;
SET #PO_ErrorMessage = '';
BEGIN TRY
exec sp_executesql #Cursor;
SET #PO_Step = 1;
OPEN crsDTO;
BEGIN TRY
FETCH NEXT FROM crsDTO INTO #ID, #Price, #Count;
while 0 = ##fetch_status
BEGIN
IF(#IsInStock = 0) BEGIN
IF(#Amount is not null and #AmountPercentage is null) BEGIN
IF EXISTS (SELECT ID FROM Business.Products WHERE ID = #ID) BEGIN
UPDATE Business.Products SET
Price = #Price + #Amount
WHERE ID = #ID
END
END else IF(#AmountPercentage is not null and #Amount is null) BEGIN
IF EXISTS (SELECT ID FROM Business.Products WHERE ID = #ID) BEGIN
UPDATE Business.Products SET
Price = (#Price * (#AmountPercentage / 100))
WHERE ID = #ID
END
END
END ELSE IF(#IsInStock = 1) BEGIN
IF(#Amount is not null and #AmountPercentage is null) BEGIN
IF EXISTS (SELECT ID FROM Business.Products WHERE ID = #ID) BEGIN
UPDATE Business.Products SET
Price = #Price + #Amount,
Count = 0
WHERE ID = #ID
END
END else IF(#AmountPercentage is not null and #Amount is null) BEGIN
IF EXISTS (SELECT ID FROM Business.Products WHERE ID = #ID) BEGIN
UPDATE Business.Products SET
Price = (#Price * (#AmountPercentage / 100)),
Count = 0
WHERE ID = #ID
END
END ELSE IF(#Amount is null and #AmountPercentage is null) BEGIN
IF EXISTS (SELECT ID FROM Business.Products WHERE ID = #ID) BEGIN
UPDATE Business.Products SET
Count = 0
WHERE ID = #ID
END
END
END
SET #PO_Step = 2;
FETCH NEXT FROM crsDTO INTO #ID, #Price, #Count;
END;
CLOSE crsDTO;
DEALLOCATE crsDTO;
END TRY
BEGIN CATCH
CLOSE crsDTO;
DEALLOCATE crsDTO;
SET #PO_Error = ERROR_NUMBER();
SET #PO_ErrorMessage = ERROR_MESSAGE();
END CATCH
END TRY
BEGIN CATCH
SET #PO_Error = ERROR_NUMBER();
SET #PO_ErrorMessage = ERROR_MESSAGE();
END CATCH
END;
I would add check if cursor exists:
-- ....
BEGIN CATCH
IF CURSOR_STATUS('global','crsDTO')>=-1
BEGIN
CLOSE crsDTO;
DEALLOCATE crsDTO;
END
END CATCH
db<>fiddle demo
Using global cursor/row-by-row approach does not seems to be the best solution.

How to increase cursor performance in sql

i have used trigger for recalculation multiple data.
in trigger i have used cursor.in cursor i have used another sp's which is contain 1 more cursor.
so it's very slow for calculation of only 80 rows data. and it's take 20 second to calculate the data. so please give me any suggestion or solution for it quickly.
thanks
here is my trigger
/****** Object: Trigger [dbo].[tr_TransactionJournal_InsteadOfDelete] Script Date: 12/23/2015 3:13:09 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[tr_TransactionJournal_InsteadOfDelete]
ON [dbo].[TransactionJournal]
INSTEAD OF DELETE
AS
SET NOCOUNT ON;
DECLARE
#TxNum INT,
#TxDate DATE
/*
declare #TempDetailTable to figure out which ChartAcctCode & Item needs to be recalculated
*/
DECLARE #TempDetailTable TABLE (
ChartAcctCode NVARCHAR(10),
ItemCode NVARCHAR(10)
)
DECLARE #MyCursor_1 CURSOR
SET #MyCursor_1 = CURSOR FAST_FORWARD
FOR
SELECT TxNum,TxDate FROM DELETED
OPEN #MyCursor_1
FETCH NEXT FROM #MyCursor_1
INTO #TxNum,#TxDate
WHILE ##fetch_status = 0 -- This is executed as long as the previous fetch succeeds.
BEGIN
------------------
BEGIN TRANSACTION
INSERT INTO #TempDetailTable
SELECT DISTINCT tjd.ChartAcctCode, tjd.ItemCode
FROM dbo.TransactionJournalDetail AS tjd
WHERE (tjd.TxNum = #TxNum)-- AND tjd.ItemCode IS NOT NULL
COMMIT TRANSACTION
/*
Actually deleting FROM TxJournal & TxJournalDetail
*/
DELETE FROM TransactionJournal WHERE TxNum=#TxNum
/*
Start Recalculating...
*/
DECLARE #MyChartAcctCode NVARCHAR(10)
DECLARE #MyItemCode NVARCHAR(10)
DECLARE #MyCursor CURSOR
/*
1st Loop to RecalcQAV if Detail has any ChartAcctCode = "AINV"
*/
SET #MyCursor = CURSOR FAST_FORWARD
FOR
SELECT ItemCode FROM #TempDetailTable WHERE ChartAcctCode = 'INV'
OPEN #MyCursor
FETCH NEXT FROM #MyCursor
INTO #MyItemCode
WHILE ##fetch_status = 0
BEGIN
EXEC RecalcQAV #MyItemCode, #TxDate;
FETCH NEXT FROM #MyCursor
INTO #MyItemCode
END
/*
2nd Loop to RecalcAcctBalance if Detail has any ChartAcctCode = "AINV"
*/
SET #MyCursor = CURSOR FAST_FORWARD
FOR
SELECT ChartAcctCode FROM #TempDetailTable
OPEN #MyCursor
FETCH NEXT FROM #MyCursor
INTO #MyChartAcctCode
WHILE ##fetch_status = 0
BEGIN
EXEC RecalcAcctBalance #MyChartAcctCode, #TxDate;
FETCH NEXT FROM #MyCursor
INTO #MyChartAcctCode
END
CLOSE #MyCursor;
DEALLOCATE #MyCursor;
------------------
FETCH NEXT FROM #MyCursor_1
INTO #TxNum,#TxDate
END
CLOSE #MyCursor_1;
DEALLOCATE #MyCursor_1;
ALTER PROCEDURE [dbo].[RecalcAcctBalance]
(
#ChartAcctCode NVARCHAR(10),
#BeginDate DATE
)
AS BEGIN
SET NOCOUNT ON;
DECLARE #LastDate DATE
SELECT #LastDate = MAX(tj.TxDate)
FROM dbo.TransactionJournal AS tj
JOIN dbo.TransactionJournalDetail AS tjd ON tj.TxNum = tjd.TxNum
WHERE tj.TxDate < #BeginDate
AND tjd.ChartAcctCode = #ChartAcctCode
DECLARE #MyTable TABLE (
AutoId INT IDENTITY(1,1) PRIMARY KEY,
TxDate DATE,
TxDetailId INT
)
INSERT INTO #MyTable (TxDate, TxDetailId)
SELECT tj.TxDate, tjd.AutoId
FROM dbo.TransactionJournal AS tj
JOIN dbo.TransactionJournalDetail AS tjd ON tj.TxNum = tjd.TxNum
WHERE tj.TxDate >= ISNULL(#LastDate, #BeginDate)
AND tjd.ChartAcctCode = #ChartAcctCode
ORDER BY tj.TxDate, tj.SourceDocOrder, tjd.AutoId
DECLARE
#LastDateMaxId INT,
#TxDetailId INT,
#LChartAcctBalance MONEY = 0
IF #LastDate IS NOT NULL
BEGIN
SELECT #TxDetailId = TxDetailId
FROM (
SELECT TxDetailId, RowNum = ROW_NUMBER() OVER (ORDER BY AutoId DESC)
FROM #MyTable
WHERE TxDate = #LastDate
) t
WHERE t.RowNum = 1
SELECT #LChartAcctBalance = ISNULL(ChartAcctBalance, 0)
FROM dbo.TransactionJournalDetail
WHERE AutoId = #TxDetailId
END
;WITH cte AS
(
SELECT *, val = #LChartAcctBalance + SUM(Amount) OVER (ORDER BY AutoId)
FROM dbo.TransactionJournalDetail
WHERE AutoId IN (
SELECT TxDetailId
FROM #MyTable
WHERE TxDate >= #BeginDate
)
)
UPDATE cte
SET ChartAcctBalance = val
END
the second sp is too large for refactoring... anyway the main problem - business logic with processing by item...

Execute a Stored Procedure with OUTPUT For Each Row in SELECT Statement

Is it possible to execute a stored procedure for each row in a SELECT? This only executes the first row, looking for something to execute for all rows:
Declare
#Loop bit = 1, #ID int, #Exists bit, #ReturnValue bit = 0
WHILE (#Loop) = 1
BEGIN
SELECT #ID = ID FROM Table --Multiple Rows Returned
EXEC [dbo].[StoredProc1] --Exec SP for Each Row
#InputID = #ID
,#Exists = #Exists OUTPUT
IF #Exists = 1
BEGIN
SET #Loop = 0
SET #ReturnValue = 1
END
END
SELECT #ReturnValue [ReturnValue]
Use a cursor:
DECLARE #exists bit
DECLARE db_cursor CURSOR FOR
SELECT ID
FROM Table
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #ID
WHILE ##FETCH_STATUS = 0
BEGIN
EXEC [dbo].[StoredProc1] --Exec SP for Each Row
#ID
, #exists OUTPUT
FETCH NEXT FROM db_cursor INTO #id
END
CLOSE db_cursor
DEALLOCATE db_cursor

Resources