How to commit inside a CURSOR Loop? - sql-server

I am trying to see if its possible to perform Update within a cursor loop and this updated data gets reflected during the second iteration in the loop.
DECLARE cur CURSOR
FOR SELECT [Product], [Customer], [Date], [Event] FROM MyTable
WHERE [Event] IS NULL
OPEN cur
FETCH NEXT INTO #Product, #Customer, #Date, #Event
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT * FROM MyTable WHERE [Event] = 'No Event' AND [Date] < #DATE
-- Now I update my Event value to 'No Event' for records whose date is less than #Date
UPDATE MyTable SET [Event] = 'No Event' WHERE [Product] = #Product AND [Customer] = #Customer AND [Date] < #DATE
FETCH NEXT INTO #Product, #Customer, #Date, #Event
END
CLOSE cur
DEALLOCATE cur
Assume when the sql executes the Event column is NULL for all records
In the above sql, I am doing a select inside the cursor loop to query MyTable where Event value is 'No Event' but the query returns no value even though I am doing an update in the next line.
So, I am thinking if it is even possible to update a table and the updated data get reflected in the next iteration of the cursor loop.
Thanks for any help,
Javid

Firstly You shouldn't need a cursor here. Something like the following would have the same semantics (from a starting position where all Event are NULL) and be more efficient.
WITH T
AS (SELECT [Event],
RANK() OVER (PARTITION BY [Product], [Customer]
ORDER BY [Date] DESC) AS Rnk
FROM MyTable)
UPDATE T
SET [Event] = 'No Event'
WHERE Rnk > 1
Secondly regarding the question in the title to commit inside a cursor loop is the same as anywhere else. You just need a COMMIT statement. However if you aren't running this inside a larger transaction the UPDATE statement will be auto committed anyway.
Thirdly Your real question doesn't seem to be about commit anyway. It is about the cursor reflecting updates to the data on subsequent iterations. For the case in the question you would need a DYNAMIC cursor
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.
Not all queries support dynamic cursors. The code in the question would but without an ORDER BY it is undeterministic what order the rows would be processed in and thus whether you would see visible results. I have added an ORDER BY and an index to support this to allow a dynamic cursor to be used.
If you try the following you will see the cursor only fetches one row as the dates are processed in descending order and when the first row is processed the table is updated such that no more rows qualify for the next fetch. If you comment out the UPDATE inside the cursor loop all three rows are fetched.
CREATE TABLE MyTable
(
[Product] INT,
[Customer] INT,
[Date] DATETIME,
[Event] VARCHAR(10) NULL,
PRIMARY KEY ([Date], [Product], [Customer])
)
INSERT INTO MyTable
VALUES (1,1,'20081201',NULL),
(1,1,'20081202',NULL),
(1,1,'20081203',NULL)
DECLARE #Product INT,
#Customer INT,
#Date DATETIME,
#Event VARCHAR(10)
DECLARE cur CURSOR DYNAMIC TYPE_WARNING FOR
SELECT [Product],
[Customer],
[Date],
[Event]
FROM MyTable
WHERE [Event] IS NULL
ORDER BY [Date] DESC
OPEN cur
FETCH NEXT FROM cur INTO #Product, #Customer, #Date, #Event
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #Product,
#Customer,
#Date,
#Event
-- Now I update my Event value to 'No Event' for records whose date is less than #Date
UPDATE MyTable
SET [Event] = 'No Event'
WHERE [Product] = #Product
AND [Customer] = #Customer
AND [Date] < #Date
FETCH NEXT FROM cur INTO #Product, #Customer, #Date, #Event
END
CLOSE cur
DEALLOCATE cur
DROP TABLE MyTable

Even if this worked, this would not guarantee the correct result since you miss an ORDER BY clause in your query.
Depending on this, all records, no records or any random subset of records could be updated.
Could you please explain in plain English what your stored procedure should do?

Use Below template
DECLARE #CCount int = 100
DECLARE #Count int = 0
DECLARE #id AS BigInt
DECLARE cur Cursor fast_forward for
SELECT t1.Id
FROM Table1 t1 WITH (NOLOCK) WHERE <Some where clause>
OPEN cur
Begin Tran
While (1=1)
Begin
Fetch next from cur into #id
If ##Fetch_Status <> 0
break
-- do some DML actions
Delete From Table1 WITH (ROWLOCK) where Id = #id
Set #count = #count + ##Rowcount
if (#count % #CCount = 0)
Begin
if (#count % 100 = 0)
Print 'Table1: DML action ' + Cast(#count as Varchar(15)) + ' rows'
-- for every 100 rows commit tran will trigger , and starts a new one.
While ##Trancount > 0 Commit Tran
Begin Tran
End
End
While ##Trancount > 0 Commit Tran
Close cur
Deallocate cur

Related

SQL Server : calling a stored procedure using table data

I may be wording this question very poorly but I am not 100% sure what I need to question.
I am trying to iterate over rows in a table and call a stored procedure using the data from the rows.
This is the code I already have, the problem with this is a timing issue (1000 rows takes around 1 minute);
--Set up a temp table with all non email alerts
SELECT TOP(1000)
RowNum = ROW_NUMBER() OVER(ORDER BY AlertID),
a.*, i.ImgData
INTO
#temp
FROM
dbo.ALERTS a
JOIN
dbo.IMAGES i ON i.VehicleID = a.VehicleID
WHERE
a.EmailImageSent = 0 OR a.EmailSent = 0
DECLARE #MaxRownum INT
SET #MaxRownum = (SELECT MAX(RowNum) FROM #temp)
DECLARE #Iter INT
SET #Iter = (SELECT MIN(RowNum) FROM #temp)
DECLARE #ImgData VARBINARY(MAX)
WHILE #Iter <= #MaxRownum
BEGIN
SELECT #VehicleID = VehicleID, #ImgData = ImgData
FROM #temp
WHERE RowNum = #Iter
IF #ImgData IS NOT NULL
BEGIN
EXEC dbo.someProcedure #VehicleID, #ImgData
--SELECT 'Image data found for', #VehicleID, #ImgData
END
SET #Iter = #Iter + 1
END
DROP TABLE #temp
Is there anyway I can run the stored procedure (dbo.someProcedure) while using a set based statement as the input?
Sorry if this has been asked before, I've had a look and couldn't find an answer or if this question isn't informative enough.
Thanks in advance
AFAIK sp_send_dbmail will need to be called once for each email, so either you have a loop here or you have a loop inside dbo.someProcedure.
Still I think that you could make some improvements. Use a FAST_FORWARD cursor rather than creating iteration variables and returning to the table each time to find the next row (thus creating 1000 table scans). Don't store redundant data in your #temp table, only what you need. This makes the table quicker to read.
Try this:
--Set up a temp table with all non email alerts
Create Table #temp (VehicleID int Primary Key Clustered, ImgData varbinary(max));
INSERT INTO #temp (VehicleID, ImgData)
SELECT TOP(1000)
a.VehicleID, i.ImgData
FROM
dbo.ALERTS a
JOIN
dbo.IMAGES i ON i.VehicleID = a.VehicleID
WHERE
a.EmailImageSent = 0 OR a.EmailSent = 0;
DECLARE #VehicleID int;
DECLARE #ImgData VARBINARY(MAX);
DECLARE Alert_Cursor Cursor Fast_Forward For (
Select VehicleID, ImgData From #temp);
OPEN Alert_Cursor;
FETCH NEXT FROM Alert_Cursor INTO #VehicleID, #ImgData;
WHILE ##FETCH_STATUS = 0
BEGIN
IF #ImgData IS NOT NULL
EXEC dbo.someProcedure #VehicleID, #ImgData;
FETCH NEXT FROM Alert_Cursor INTO #VehicleID, #ImgData;
END
CLOSE Alert_Cursor;
DEALLOCATE Alert_Cursor;
DROP TABLE #temp;

Run stored procedure from sql job based on select results

I have a sql table with four fields a,b,c,d and a stored procedure sp_x.
How can I create a job that will run periodically and do two things (with one select):
update field c and field d for rows that have b=1;
And after that execute sp_x passing it field a but only for rows that have b=1
You could use "output" of the update statement to receive the row id of the columns you have just updated and write this data into a temporary table or table variable.
This then allows more statements to be used to process the logging activity.
You could also consider using on update triggers on your table that perform the logging when your status changes.
use [tempdb];
go
create table [outputexample]
(
[rowid] int identity(1,1)
, [datetime] datetime default (getdate()) not null
, [status] varchar(20) not null
);
insert into [outputexample] ([status])
select 'Active' union all
select 'Closed' union all
select 'Active'
go
Scheduled job code:
declare #updatedrow table
(
[rowid] int
);
update [outputexample]
set [datetime] = getdate()
, [status] = 'Closed'
output inserted.[rowid]
into #updatedrow
where [datetime] < getdate()
and [status] <> 'Closed';
declare #rowid int;
declare cursor_updatedrow cursor for
select [rowid] from #updatedrow;
open cursor_updatedrow;
fetch next from cursor_updatedrow into #rowid;
while ##fetch_status = 0
begin
-- exec sp_myauditsp #rowid = #rowid
print cast(#rowid as varchar(20)) + ' was updated to closed.'
fetch next from cursor_updatedrow into #rowid;
end
close cursor_updatedrow;
deallocate cursor_updatedrow;
updating the rows you want:
UPDATE mytable SET C=..., D=... WHERE B=1
executing stored proc for certain rows only:
DECLARE #a int
DECLARE my_cursor CURSOR
FOR SELECT a FROM jobtable WHERE b=1
OPEN my_cursor
FETCH NEXT FROM my_cursor INTO #a
WHILE ##FETCH_STATUS=0
BEGIN
EXEC sp_x #a
END
FETCH NEXT FROM my_cursor into #a
CLOSE my_cursor
DEALLOCATE my_Cursor
All inside a job that runs when you want

Deadlock on Insert statement

Inserting a series of records from my listview to my SQL Server table using a stored procedure. However I get a deadlock like the image below
This is my procedure:
declare #debtorCollectorHistoryId bigint
set #debtorCollectorHistoryId = (Select top 1 id
from FVOfficer with(nolock)
where debtorid = #debtorid
order by id desc)
if #currentFvCollectorid is null
set #currentFvCollectorid = (SELECT FvCollectorID
from Debtor with(nolock)
where id = #debtorid)
if #assignon is null
set #assignon = GETDATE()
print #debtorCollectorHistoryId
print #currentFvCollectorid
print #newFvCollectorid
if #currentFvCollectorid <> #newFvCollectorid
BEGIN
if #currentFvCollectorid <> 0
Begin
update FVOfficer
set terminateon = GetDate()
where id = #debtorCollectorHistoryId
End
Begin
--create new Collector History for new collector
--NOTE: CurrentCollectorID is a previous collectorID for new Collector History
insert into FVOfficer (debtorid, prevCollectorID, collectorid, assignon, terminateon, loginID)
values (#debtorid,#currentFvCollectorid, #newFvCollectorid, GetDate(), null, #loginID)
print' create new Collector History for new FV collector'
exec dbo.[ValidatePrevFVCollectorByDebtorID] #debtorid
end
begin
print #debtorid
update Debtor set FVCollectorid = #newFvCollectorid where id = #debtorid
end
From my snapshot , i am assuming the deadlock happens on the table 'FVOfficer' , but my currentFvCollectorid is always 0 and therefore no update statement is performed . However, i pass my #debtorid to the procedure [ValidatePrevCollectorByDebtorID] which perform some validation with a cursor on a different table not FVOfficer table. Could it be that the parameter #debtorid is locked by the second procedure and while its required in the insert ? if so please how do i resolve this.
When i check my table FVOfficer , i noticed it has no primary key or any index, I index the id column which is an identity(1,1) column . I created a clustered index on the id and this does not solve my problem. Please how do i proceed with this ?
Update
Below is my [ValidatePrevFVCollectorByDebtorID] please does it have anything to do with the issue ?
DECLARE #ID AS BIGINT
--LOOP ALL THE DEBTOR COLLECTOR HISTORY ID
----------------------------------------------------------------------------
DECLARE LOOPDEBTOR_CURSOR CURSOR
FOR SELECT ID FROM DEBTOR WITH(NOLOCK) WHERE ID = #REFDEBTORID
--WHERE COLLECTORID > 0
OPEN LOOPDEBTOR_CURSOR
FETCH NEXT FROM LOOPDEBTOR_CURSOR INTO #ID
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #COLLECTORYHISTORYID AS BIGINT
DECLARE #ASSIGNON AS DATETIME
DECLARE #DEBTORID AS BIGINT
DECLARE #COLLECTORID AS BIGINT
DECLARE #PREVCOLLECTORID AS BIGINT
--GET THE 1ST HISTORY ID BASE ON DEBTORID
---------------------------------------------------------------------------
DECLARE #FIRSTID AS BIGINT
SET #FIRSTID = (SELECT TOP 1 ID FROM FVOFFICER WITH(NOLOCK)
WHERE DEBTORID = #ID ORDER BY ID )
-- LIST ALL THE DEBTOR COLLECTOR HISTORY BASE ON DEBTORID = #ID
---------------------------------------------------------------------------
DECLARE LOOPHISTORY_CURSOR CURSOR
FOR SELECT ID,DEBTORID,COLLECTORID,ASSIGNON FROM FVOFFICER
WHERE DEBTORID = #ID ORDER BY ID ASC
OPEN LOOPHISTORY_CURSOR
FETCH NEXT FROM LOOPHISTORY_CURSOR INTO
#COLLECTORYHISTORYID,#DEBTORID,#COLLECTORID,#ASSIGNON
WHILE ##FETCH_STATUS = 0
BEGIN
--FIX PREV COLLECTOR ID VALUE
IF #FIRSTID != #COLLECTORYHISTORYID
UPDATE FVOFFICER SET PREVCOLLECTORID = #PREVCOLLECTORID
WHERE ID = #COLLECTORYHISTORYID
SET #PREVCOLLECTORID = #COLLECTORID
FETCH NEXT FROM LOOPHISTORY_CURSOR INTO
#COLLECTORYHISTORYID,#DEBTORID,#COLLECTORID,#ASSIGNON
END
CLOSE LOOPHISTORY_CURSOR
DEALLOCATE LOOPHISTORY_CURSOR
FETCH NEXT FROM LOOPDEBTOR_CURSOR INTO #ID
END
CLOSE LOOPDEBTOR_CURSOR
DEALLOCATE LOOPDEBTOR_CURSOR
Sorry i posted the wrong proc as my 'ValidatePrevFVCollectorByDebtorID'. After careful deliberation . I understand that the lock happened in the procedure ValidatePrevFVCollectorByDebtorID at
DECLARE LOOPHISTORY_CURSOR CURSOR
FOR SELECT ID,DEBTORID,COLLECTORID,ASSIGNON FROM FVOFFICER
WHERE DEBTORID = #ID ORDER BY ID ASC
OPEN LOOPHISTORY_CURSOR
The debtorid was locked above. Since i am executing a bulk insert in a loop, i new insert instance was called , however the debtorid was locked on the previous task/cursor. First
1 I added the keyword with(nolock) at the select statement like below
SELECT ID,DEBTORID,COLLECTORID,ASSIGNON FROM FVOFFICER with(nolock)
WHERE DEBTORID = #ID ORDER BY ID ASC
Then i added a non-clustered index on the column debtorid on the table FVOfficer to enhance the speed of my select statements. After i run, the deadlock didn't happened .

little problem with mssql cursor, insert in temp table within the cursor

i'm having a little trouble with my stored procedure.
I've written a stored procedure where i'm using a cursor.
Every thing works fine till the place where i'm inserting values from the cursor to the temp table.
here is the error
Msg 156, Level 15, State 1, Procedure ors_DailyReportMessageStatus, Line 36
Incorrect syntax near the keyword 'where'.
and here is the code
ALTER PROCEDURE [dbo].[ors_DailyReportMessageStatus]
-- Add the parameters for the stored procedure here
#startDate datetime, #endDate datetime
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
DECLARE #num int;
DECLARE #stat varchar(20);
DECLARE #statusCursor CURSOR
SET #statusCursor = CURSOR FOR
select count(ms.status) as message, ms.status from message_status ms JOIN [message] m on m.id=ms.message_id
where (m.ADDED_ON >= #startDate AND m.ADDED_ON < #endDate) GROUP BY ms.status
SET NOCOUNT ON;
if object_id('tempdb..#tempdailystatus') is not null
begin
drop table #tempdailystatus
end
CREATE TABLE #tempdailystatus(id int identity, total int , [status] varchar(20));
insert into #tempdailystatus ([status])
select distinct([status]) from message_status;
open #statusCursor
fetch next from #statusCursor into #num, #stat;
while ##FETCH_STATUS = 0
begin
-- this is where the error is
insert into #tempdailystatus (total) values (#num) where id = (select id from #tempdailystatus where [status] = #stat)
-- this were just to see whether the cursor is ok. and it is
--print #stat
--print #num ;
fetch next from #statusCursor into #num,#stat
end
close #statusCursor
deallocate #statusCursor
-- Insert statements for procedure here
-- SELECT * from #tempdailystatus
drop table #tempdailystatus
END
Am I possibly ignoring something? it seems like i'm forgetting something.
Thanks you for reading this and for giving suggestion .will appreciate it ^_^
try replacing:
insert into #tempdailystatus (total) values (#num) where id = (select id from #tempdailystatus where [status] = #stat)
with:
If Exists(Select 1 From #tempdailystatus where [status] = #stat)
Begin
insert into #tempdailystatus (total) Values(#num)
End
I made some assumptions about your logic, so you may need to adjust accordingly.

Is there a way to loop through a table variable in TSQL without using a cursor?

Let's say I have the following simple table variable:
declare #databases table
(
DatabaseID int,
Name varchar(15),
Server varchar(15)
)
-- insert a bunch rows into #databases
Is declaring and using a cursor my only option if I wanted to iterate through the rows? Is there another way?
First of all you should be absolutely sure you need to iterate through each row — set based operations will perform faster in every case I can think of and will normally use simpler code.
Depending on your data it may be possible to loop using just SELECT statements as shown below:
Declare #Id int
While (Select Count(*) From ATable Where Processed = 0) > 0
Begin
Select Top 1 #Id = Id From ATable Where Processed = 0
--Do some processing here
Update ATable Set Processed = 1 Where Id = #Id
End
Another alternative is to use a temporary table:
Select *
Into #Temp
From ATable
Declare #Id int
While (Select Count(*) From #Temp) > 0
Begin
Select Top 1 #Id = Id From #Temp
--Do some processing here
Delete #Temp Where Id = #Id
End
The option you should choose really depends on the structure and volume of your data.
Note: If you are using SQL Server you would be better served using:
WHILE EXISTS(SELECT * FROM #Temp)
Using COUNT will have to touch every single row in the table, the EXISTS only needs to touch the first one (see Josef's answer below).
Just a quick note, if you are using SQL Server (2008 and above), the examples that have:
While (Select Count(*) From #Temp) > 0
Would be better served with
While EXISTS(SELECT * From #Temp)
The Count will have to touch every single row in the table, the EXISTS only needs to touch the first one.
This is how I do it:
declare #RowNum int, #CustId nchar(5), #Name1 nchar(25)
select #CustId=MAX(USERID) FROM UserIDs --start with the highest ID
Select #RowNum = Count(*) From UserIDs --get total number of records
WHILE #RowNum > 0 --loop until no more records
BEGIN
select #Name1 = username1 from UserIDs where USERID= #CustID --get other info from that row
print cast(#RowNum as char(12)) + ' ' + #CustId + ' ' + #Name1 --do whatever
select top 1 #CustId=USERID from UserIDs where USERID < #CustID order by USERID desc--get the next one
set #RowNum = #RowNum - 1 --decrease count
END
No Cursors, no temporary tables, no extra columns.
The USERID column must be a unique integer, as most Primary Keys are.
Define your temp table like this -
declare #databases table
(
RowID int not null identity(1,1) primary key,
DatabaseID int,
Name varchar(15),
Server varchar(15)
)
-- insert a bunch rows into #databases
Then do this -
declare #i int
select #i = min(RowID) from #databases
declare #max int
select #max = max(RowID) from #databases
while #i <= #max begin
select DatabaseID, Name, Server from #database where RowID = #i --do some stuff
set #i = #i + 1
end
Here is how I would do it:
Select Identity(int, 1,1) AS PK, DatabaseID
Into #T
From #databases
Declare #maxPK int;Select #maxPK = MAX(PK) From #T
Declare #pk int;Set #pk = 1
While #pk <= #maxPK
Begin
-- Get one record
Select DatabaseID, Name, Server
From #databases
Where DatabaseID = (Select DatabaseID From #T Where PK = #pk)
--Do some processing here
--
Select #pk = #pk + 1
End
[Edit] Because I probably skipped the word "variable" when I first time read the question, here is an updated response...
declare #databases table
(
PK int IDENTITY(1,1),
DatabaseID int,
Name varchar(15),
Server varchar(15)
)
-- insert a bunch rows into #databases
--/*
INSERT INTO #databases (DatabaseID, Name, Server) SELECT 1,'MainDB', 'MyServer'
INSERT INTO #databases (DatabaseID, Name, Server) SELECT 1,'MyDB', 'MyServer2'
--*/
Declare #maxPK int;Select #maxPK = MAX(PK) From #databases
Declare #pk int;Set #pk = 1
While #pk <= #maxPK
Begin
/* Get one record (you can read the values into some variables) */
Select DatabaseID, Name, Server
From #databases
Where PK = #pk
/* Do some processing here */
/* ... */
Select #pk = #pk + 1
End
If you have no choice than to go row by row creating a FAST_FORWARD cursor. It will be as fast as building up a while loop and much easier to maintain over the long haul.
FAST_FORWARD
Specifies a FORWARD_ONLY, READ_ONLY cursor with performance optimizations enabled. FAST_FORWARD cannot be specified if SCROLL or FOR_UPDATE is also specified.
This will work in SQL SERVER 2012 version.
declare #Rowcount int
select #Rowcount=count(*) from AddressTable;
while( #Rowcount>0)
begin
select #Rowcount=#Rowcount-1;
SELECT * FROM AddressTable order by AddressId desc OFFSET #Rowcount ROWS FETCH NEXT 1 ROWS ONLY;
end
Another approach without having to change your schema or using temp tables:
DECLARE #rowCount int = 0
,#currentRow int = 1
,#databaseID int
,#name varchar(15)
,#server varchar(15);
SELECT #rowCount = COUNT(*)
FROM #databases;
WHILE (#currentRow <= #rowCount)
BEGIN
SELECT TOP 1
#databaseID = rt.[DatabaseID]
,#name = rt.[Name]
,#server = rt.[Server]
FROM (
SELECT ROW_NUMBER() OVER (
ORDER BY t.[DatabaseID], t.[Name], t.[Server]
) AS [RowNumber]
,t.[DatabaseID]
,t.[Name]
,t.[Server]
FROM #databases t
) rt
WHERE rt.[RowNumber] = #currentRow;
EXEC [your_stored_procedure] #databaseID, #name, #server;
SET #currentRow = #currentRow + 1;
END
You can use a while loop:
While (Select Count(*) From #TempTable) > 0
Begin
Insert Into #Databases...
Delete From #TempTable Where x = x
End
Lightweight, without having to make extra tables, if you have an integer ID on the table
Declare #id int = 0, #anything nvarchar(max)
WHILE(1=1) BEGIN
Select Top 1 #anything=[Anything],#id=#id+1 FROM Table WHERE ID>#id
if(##ROWCOUNT=0) break;
--Process #anything
END
I really do not see the point why you would need to resort to using dreaded cursor.
But here is another option if you are using SQL Server version 2005/2008
Use Recursion
declare #databases table
(
DatabaseID int,
Name varchar(15),
Server varchar(15)
)
--; Insert records into #databases...
--; Recurse through #databases
;with DBs as (
select * from #databases where DatabaseID = 1
union all
select A.* from #databases A
inner join DBs B on A.DatabaseID = B.DatabaseID + 1
)
select * from DBs
-- [PO_RollBackOnReject] 'FININV10532'
alter procedure PO_RollBackOnReject
#CaseID nvarchar(100)
AS
Begin
SELECT *
INTO #tmpTable
FROM PO_InvoiceItems where CaseID = #CaseID
Declare #Id int
Declare #PO_No int
Declare #Current_Balance Money
While (Select ROW_NUMBER() OVER(ORDER BY PO_LineNo DESC) From #tmpTable) > 0
Begin
Select Top 1 #Id = PO_LineNo, #Current_Balance = Current_Balance,
#PO_No = PO_No
From #Temp
update PO_Details
Set Current_Balance = Current_Balance + #Current_Balance,
Previous_App_Amount= Previous_App_Amount + #Current_Balance,
Is_Processed = 0
Where PO_LineNumber = #Id
AND PO_No = #PO_No
update PO_InvoiceItems
Set IsVisible = 0,
Is_Processed= 0
,Is_InProgress = 0 ,
Is_Active = 0
Where PO_LineNo = #Id
AND PO_No = #PO_No
End
End
It's possible to use a cursor to do this:
create function [dbo].f_teste_loop
returns #tabela table
(
cod int,
nome varchar(10)
)
as
begin
insert into #tabela values (1, 'verde');
insert into #tabela values (2, 'amarelo');
insert into #tabela values (3, 'azul');
insert into #tabela values (4, 'branco');
return;
end
create procedure [dbo].[sp_teste_loop]
as
begin
DECLARE #cod int, #nome varchar(10);
DECLARE curLoop CURSOR STATIC LOCAL
FOR
SELECT
cod
,nome
FROM
dbo.f_teste_loop();
OPEN curLoop;
FETCH NEXT FROM curLoop
INTO #cod, #nome;
WHILE (##FETCH_STATUS = 0)
BEGIN
PRINT #nome;
FETCH NEXT FROM curLoop
INTO #cod, #nome;
END
CLOSE curLoop;
DEALLOCATE curLoop;
end
I'm going to provide the set-based solution.
insert #databases (DatabaseID, Name, Server)
select DatabaseID, Name, Server
From ... (Use whatever query you would have used in the loop or cursor)
This is far faster than any looping techique and is easier to write and maintain.
I prefer using the Offset Fetch if you have a unique ID you can sort your table by:
DECLARE #TableVariable (ID int, Name varchar(50));
DECLARE #RecordCount int;
SELECT #RecordCount = COUNT(*) FROM #TableVariable;
WHILE #RecordCount > 0
BEGIN
SELECT ID, Name FROM #TableVariable ORDER BY ID OFFSET #RecordCount - 1 FETCH NEXT 1 ROW;
SET #RecordCount = #RecordCount - 1;
END
This way I don't need to add fields to the table or use a window function.
I agree with the previous post that set-based operations will typically perform better, but if you do need to iterate over the rows here's the approach I would take:
Add a new field to your table variable (Data Type Bit, default 0)
Insert your data
Select the Top 1 Row where fUsed = 0 (Note: fUsed is the name of the field in step 1)
Perform whatever processing you need to do
Update the record in your table variable by setting fUsed = 1 for the record
Select the next unused record from the table and repeat the process
DECLARE #databases TABLE
(
DatabaseID int,
Name varchar(15),
Server varchar(15),
fUsed BIT DEFAULT 0
)
-- insert a bunch rows into #databases
DECLARE #DBID INT
SELECT TOP 1 #DBID = DatabaseID from #databases where fUsed = 0
WHILE ##ROWCOUNT <> 0 and #DBID IS NOT NULL
BEGIN
-- Perform your processing here
--Update the record to "used"
UPDATE #databases SET fUsed = 1 WHERE DatabaseID = #DBID
--Get the next record
SELECT TOP 1 #DBID = DatabaseID from #databases where fUsed = 0
END
Step1: Below select statement creates a temp table with unique row number for each record.
select eno,ename,eaddress,mobno int,row_number() over(order by eno desc) as rno into #tmp_sri from emp
Step2:Declare required variables
DECLARE #ROWNUMBER INT
DECLARE #ename varchar(100)
Step3: Take total rows count from temp table
SELECT #ROWNUMBER = COUNT(*) FROM #tmp_sri
declare #rno int
Step4: Loop temp table based on unique row number create in temp
while #rownumber>0
begin
set #rno=#rownumber
select #ename=ename from #tmp_sri where rno=#rno **// You can take columns data from here as many as you want**
set #rownumber=#rownumber-1
print #ename **// instead of printing, you can write insert, update, delete statements**
end
This approach only requires one variable and does not delete any rows from #databases. I know there are a lot of answers here, but I don't see one that uses MIN to get your next ID like this.
DECLARE #databases TABLE
(
DatabaseID int,
Name varchar(15),
Server varchar(15)
)
-- insert a bunch rows into #databases
DECLARE #CurrID INT
SELECT #CurrID = MIN(DatabaseID)
FROM #databases
WHILE #CurrID IS NOT NULL
BEGIN
-- Do stuff for #CurrID
SELECT #CurrID = MIN(DatabaseID)
FROM #databases
WHERE DatabaseID > #CurrID
END
Here's my solution, which makes use of an infinite loop, the BREAK statement, and the ##ROWCOUNT function. No cursors or temporary table are necessary, and I only need to write one query to get the next row in the #databases table:
declare #databases table
(
DatabaseID int,
[Name] varchar(15),
[Server] varchar(15)
);
-- Populate the [#databases] table with test data.
insert into #databases (DatabaseID, [Name], [Server])
select X.DatabaseID, X.[Name], X.[Server]
from (values
(1, 'Roger', 'ServerA'),
(5, 'Suzy', 'ServerB'),
(8675309, 'Jenny', 'TommyTutone')
) X (DatabaseID, [Name], [Server])
-- Create an infinite loop & ensure that a break condition is reached in the loop code.
declare #databaseId int;
while (1=1)
begin
-- Get the next database ID.
select top(1) #databaseId = DatabaseId
from #databases
where DatabaseId > isnull(#databaseId, 0);
-- If no rows were found by the preceding SQL query, you're done; exit the WHILE loop.
if (##ROWCOUNT = 0) break;
-- Otherwise, do whatever you need to do with the current [#databases] table row here.
print 'Processing #databaseId #' + cast(#databaseId as varchar(50));
end
This is the code that I am using 2008 R2. This code that I am using is to build indexes on key fields (SSNO & EMPR_NO) n all tales
if object_ID('tempdb..#a')is not NULL drop table #a
select 'IF EXISTS (SELECT name FROM sysindexes WHERE name ='+CHAR(39)+''+'IDX_'+COLUMN_NAME+'_'+SUBSTRING(table_name,5,len(table_name)-3)+char(39)+')'
+' begin DROP INDEX [IDX_'+COLUMN_NAME+'_'+SUBSTRING(table_name,5,len(table_name)-3)+'] ON '+table_schema+'.'+table_name+' END Create index IDX_'+COLUMN_NAME+'_'+SUBSTRING(table_name,5,len(table_name)-3)+ ' on '+ table_schema+'.'+table_name+' ('+COLUMN_NAME+') ' 'Field'
,ROW_NUMBER() over (order by table_NAMe) as 'ROWNMBR'
into #a
from INFORMATION_SCHEMA.COLUMNS
where (COLUMN_NAME like '%_SSNO_%' or COLUMN_NAME like'%_EMPR_NO_')
and TABLE_SCHEMA='dbo'
declare #loopcntr int
declare #ROW int
declare #String nvarchar(1000)
set #loopcntr=(select count(*) from #a)
set #ROW=1
while (#ROW <= #loopcntr)
begin
select top 1 #String=a.Field
from #A a
where a.ROWNMBR = #ROW
execute sp_executesql #String
set #ROW = #ROW + 1
end
SELECT #pk = #pk + 1
would be better:
SET #pk += #pk
Avoid using SELECT if you are not referencing tables are are just assigning values.

Resources