Reputation: 11
I need some help with a SQL Server cursor:
DECLARE @iTerminalID varchar(100)
DECLARE @iID int
DECLARE @iDateTB date
DECLARE @iNom bigint
DECLARE @iTBtoEOD bigint
DECLARE @iSal bigint
DECLARE @iOldNom bigint
DECLARE @iOldSal bigint
DECLARE @check int
DECLARE main_cursor CURSOR FOR
SELECT
TerminalId
FROM AA
WHERE Nom IS NULL
GROUP BY TerminalId
ORDER BY TerminalId
OPEN main_cursor
FETCH NEXT
FROM main_cursor INTO @iTerminalID
WHILE (@@fetch_status = 0)
BEGIN
SET @iOldSal = 0
DECLARE detail_cursor CURSOR FOR
SELECT
ROW_NUMBER() OVER (ORDER BY TerminalId, DateTB) NoID,
DateTB,
Nomi,
TBtoEOD,
Sal
FROM AA
WHERE TerminalId = @iTerminalID
OPEN detail_cursor
FETCH NEXT
FROM detail_cursor INTO @iID, @iDateTB, @iNom, @iTBtoEOD, @iSal
WHILE (@@fetch_status = 0)
BEGIN
IF @iNom IS NULL AND @iID = 1
BEGIN
UPDATE AA
SET Nom = 0
WHERE TerminalId = @iTerminalID
AND CONVERT(date, TanggalTB) = CONVERT(date, @iDateTB)
SET @iOldSal = abs(@iTBtoEOD)
END
ELSE IF @iNom IS NULL
AND @iID <> 1
--AND @iOldSal <> 0
BEGIN
UPDATE AA
SET Nom = @iOldSal
WHERE TerminalId = @iTerminalID
AND CONVERT(date, DateTB) = CONVERT(date, @iDateTB)
PRINT concat(@iTerminalID, '----', @iDateTB)
SET @iOldSal = @iNom - @iTBtoEOD
END
ELSE
BEGIN
SET @iOldSal = @iNom - @iTBtoEOD
END
FETCH detail_cursor INTO @iID, @iDateTB, @iNom, @iTBtoEOD, @iSal
END
CLOSE detail_cursor
DEALLOCATE detail_cursor
FETCH main_cursor INTO @iTerminalID
END
CLOSE main_cursor
DEALLOCATE main_cursor
I have 2 cursors The first cursor for looping to the detailed data after i get the detail of the data that I have, I will perform the update based on the calculation that I have written
the problem here is not done until the update process is completed by the cursor to 2
Upvotes: 0
Views: 322
Reputation: 6612
As marc_s suggested it is best to get rid of SQL cursor on a SQL Server development solution unless you have no other choice.
I have prepared following T-SQL Update command where no cursor is used
UPDATE AA
set
Nomi = case when ( (NewAA.NoID = 1) and (AA.Nomi is null) )
then 0
when ( (NewAA.NoID > 1) and (AA.Nomi is null) )
then (select sum(t.Nomi) from AA t where t.DateTB < AA.DateTB and t.TerminalId = AA.TerminalId)
else AA.Nomi
end
FROM AA
Inner Join (
SELECT
PK_Field,
NoID = ROW_NUMBER() OVER (Partition By TerminalId ORDER BY DateTB),
DateTB,
Nomi,
TBtoEOD,
Sal
FROM AA
) NewAA
on AA.PK_Field = NewAA.PK_Field
Please note that I have used the Primary Key field named PK_Field. I hope you have primary key on your table AA
I'm not sure about the Update statement details, please test above sql code on a test environment first
Upvotes: 1
Reputation: 9299
review diff on every step
- specify type/direction of cursor
- not group_by but distinct, don't confuse yourself
- in row_number unnecessary sorting by TerminalId while filtering by single @iTerminalId
- save same syntax for fetches and other statements
- looks like you need row_numbered rows in ascending order - you have to specify order_by explicitly
DECLARE
@main_cursor cursor,
@detail_cursor cursor
SET @main_cursor = CURSOR FAST_FORWARD FOR
SELECT DISTINCT
TerminalId
FROM AA
WHERE Nom IS NULL
ORDER BY TerminalId
OPEN @main_cursor
FETCH NEXT FROM @main_cursor
INTO @iTerminalID
WHILE (@@fetch_status = 0)
BEGIN
SET @iOldSal = 0
SET @detail_cursor = CURSOR FAST_FORWARD FOR
SELECT
ROW_NUMBER() OVER (ORDER BY DateTB) as NoID,
DateTB, Nomi, TBtoEOD, Sal
FROM AA
WHERE TerminalId = @iTerminalID
ORDER BY NoID
OPEN @detail_cursor
FETCH NEXT FROM @detail_cursor
INTO @iID, @iDateTB, @iNom, @iTBtoEOD, @iSal
WHILE (@@fetch_status = 0)
BEGIN
... same inner part for now
FETCH NEXT FROM @detail_cursor
INTO @iID, @iDateTB, @iNom, @iTBtoEOD, @iSal
END
CLOSE @detail_cursor
DEALLOCATE @detail_cursor
FETCH NEXT FROM @main_cursor
INTO @iTerminalID
END
CLOSE @main_cursor
DEALLOCATE @main_cursor
- outer loop does nothing but zeroing @iOldSal
- another thing - row_number starting with 1 for every TerminalID
- so all the data needed in right order may be obtained with single query
SET @detail_cursor = CURSOR FAST_FORWARD FOR
SELECT
a.TerminalId,
ROW_NUMBER() OVER (PARTITION BY a.TerminalId ORDER BY a.DateTB) as NoID,
a.DateTB, a.Nomi, a.TBtoEOD, a.Sal
FROM AA a
ORDER BY a.TerminalId, NoID
OPEN @detail_cursor
FETCH NEXT FROM @detail_cursor
INTO @TerminalId, @iID, @iDateTB, @iNom, @iTBtoEOD, @iSal
WHILE (@@fetch_status = 0)
BEGIN
if @iID = 1
SET @iOldSal = 0
... same inner part for now
FETCH NEXT FROM @detail_cursor
INTO @TerminalId, @iID, @iDateTB, @iNom, @iTBtoEOD, @iSal
END
CLOSE @detail_cursor
DEALLOCATE @detail_cursor
Following changes I'm performing assuming you are renumbering rows per each terminal in DateTB order and that you have a typo in first update (TanggalTB column instead of DateTB) and that TBtoEOD is some kind of offset which is always negative and that every update affects only one row. These assumptions may be incorrect - feel free to note in comments.
- @iOldSal computation is always the same except @iNum=NULL which can be handled with help of IsNull()
- when @iID=1 @iOldSal is always 0 so first IF makes no sense - it's the same as second
- when @iNom is NULL
@iNom - @iTBtoEOD
will return NULL - you have to initialize it- cursor processing means you really have a cursor positioned on currently processed row so you don't have to locate it again in update statement; but you need to modify cursor type (FAST_FORWARD contains READONLY) to FORWARD_ONLY (probably with FOR_UPDATE - just can't remember; could be sqlserver will not allow it to be "FOR_UPDATE" because of window function, I did not check)
- many variables become unnecessary because we refer row values directly and don't need to repeat searching of currently processed row
SET @detail_cursor = CURSOR FORWARD_ONLY, FOR_UPDATE FOR
SELECT
ROW_NUMBER() OVER (PARTITION BY a.TerminalId ORDER BY a.DateTB) as NoID,
a.Nom
FROM AA a
ORDER BY a.TerminalId, NoID
FETCH NEXT FROM @detail_cursor
INTO @iID, @iNom
WHILE (@@fetch_status = 0)
BEGIN
IF @iID = 1
SET @iOldSal = 0
UPDATE a SET
Nom = IsNull(a.Nom, @iOldSal),
@iOldSal = a.Nom - a.TBtoEOD
FROM AA a
WHERE CURRENT OF @detail_cursor
FETCH NEXT FROM @detail_cursor
INTO @iID, @iNom
END
CLOSE @detail_cursor
DEALLOCATE @detail_cursor
So you are just setting a number to rows which don't have it based on prior numbered row? Not sure if I understood everything right but it seems to me that this task could be accomplished with code like this:
;WITH cteAA as
(
SELECT
a.TerminalID, a.TBtoEOD, a.Nom,
ROW_NUMBER() OVER (PARTITION BY a.TerminalId ORDER BY a.DateTB, a.Nom) as NoID
FROM AA a
),
cteAANums as
(
SELECT a.TerminalID, a.NoID, IsNull(a.Nom, 0) as Nom, a.TBtoEOD
FROM cteAA a
WHERE a.NoID = 1
UNION ALL
SELECT a.TerminalID, a.NoID, IsNull(a.Nom, n.Nom-n.TBtoEOD) as Nom, a.TBtoEOD
FROM cteAA a
INNER JOIN cteAANums n on n.TerminalID = a.TerminalID
WHERE a.NoID = n.NoID + 1
)
UPDATE a SET
Nom = n.Nom
FROM AA a
INNER JOIN cteAANums n on n.TerminalID = a.TerminalID and a.DateTB = n.DateTB
It cannot be accomplished without self-joins and regarding other circumstances cursor is probably not a bad alternative. But not the only one for sure.
Upvotes: 0