Why do my results not stay consistent? - sql-server

I have the following stored procedure that I working on. I have noticed that every 5th or 6th time I refresh my results there are new values in there. Which considering that the data is in a static environment and no one is making any changes to the data at this time I really can't understand. Can someone please enlighten me as to why I would see different results even though I am running this procedure with the exact same parameters. I even tried it in query analyzer and still see the same strange results.
I am running in Sql 2008.
Here is the proc:
ALTER PROCEDURE [dbo].[SelectSearchBy_Category]
#userId INT,
#page INT,
#results INT,
#category NVARCHAR(50),
#searchTerm NVARCHAR(200) = NULL
AS
BEGIN
SET NOCOUNT ON
SET ROWCOUNT #results
DECLARE #categoryId INT
IF (#category IS NOT NULL) BEGIN
SET #categoryId = ( SELECT categoryId FROM Category WHERE categoryDescription = #category )
END
DECLARE #rowEnd INT
DECLARE #rowStart INT
SET #rowEnd = (#page * #results)
SET #rowStart = #rowEnd - #results
;WITH OrderedItems AS
(
SELECT
i.itemId,
title,
i.[description],
i.url,
i.categoryId,
i.ratingId,
i.requirements,
ISNULL(i.rating, 0) AS tating,
ISNULL(i.raters, 0) AS raters,
i.urlFriendlyPath,
ROW_NUMBER() OVER
(
ORDER BY i.dateAdded, (ISNULL(i.rating, 0) * ISNULL(i.raters, 0))
) AS RowNumber
FROM
[dbo].[Item] i
LEFT JOIN
UserItemIgnore uii ON uii.itemId = i.itemId AND uii.userId = #userId
INNER JOIN
ItemLanguage il ON il.itemId = i.itemId
WHERE
(#searchTerm IS NULL OR a.title LIKE '%' + #searchTerm + '%') AND
i.categoryId = #categoryId AND
il.languageId = 1 AND
uii.itemId IS NULL
)
SELECT *
FROM OrderedItems
WHERE RowNumber BETWEEN #rowStart AND #rowEnd
END

You will probably have consistent results if you put an order by clause in your OrderedItems temporary table definition.

Try using
ROW_NUMBER() OVER (ORDER BY i.dateAdded,
(ISNULL(i.rating, 0) * ISNULL(i.raters, 0)),
i.itemId)
i.itemId will act as a tie breaker to ensure that the results of ROW_NUMBER are deterministic in the event you have rows with equal ranks for i.dateAdded, (ISNULL(i.rating, 0) * ISNULL(i.raters, 0))

Related

Query taking too long to execute with scalar function in where clause

Below is my scalar function:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [CheckClients]
(
#UserId Varchar(3),
#DbrNo varchar(10),
#V_DBR_CLIENT varchar(6)
)
RETURNS int
AS
BEGIN
Declare #Flag int
set #Flag=1
if(#V_DBR_CLIENT='XXXXXX')
BEGIN
if((select COUNT(USR_CLI)
from USRAGYCLI
inner join DBR on DBR_CLIENT = USR_CLI
where USR_CODE = #UserId and DBR_SERIES like #DbrNo +'T') <>
(select COUNT(DBR_CLIENT)
from DBR
where DBR_SERIES like #DbrNo + 'T') OR
(select COUNT(DBR_CLIENT)
from DBR
where DBR_SERIES like #DbrNo +'T') <= 0)
BEGIN
set #Flag=0
END
END
RETURN #Flag
END
This is my stored procedure:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [SEL_CLI]
#V_USER_ID VARCHAR(3),
#V_NUMBER_OF_ROWS INT,
#V_STARTS_WITH INT
AS
BEGIN
CREATE TABLE #tmpDbrNo
(
Code VARCHAR(10),
Name VARCHAR(100),
NumberOfDebtors int,
rownum int
)
;WITH Temp AS
(
SELECT
CLT_NO AS Code,
CLT_NAME AS Name,
COUNT(DBR_NO) AS NumberOfDebtors
FROM
DBR
JOIN
USRAGYCLI ON DBR_CLIENT = USR_AGY_CLI
JOIN
CLT ON DBR_CLIENT = CLT_NO
WHERE
AND USR_CODE = #V_USER_ID
AND 1 = CheckClients(#V_USER_ID, DBR_NO, DBR_CLIENT)
GROUP BY
CLT_NO, CLT_NAME
)
INSERT INTO #tmpDbrNo
SELECT
Code, Name, NumberOfDebtors,
ROW_NUMBER() OVER (ORDER by Code) rownum
FROM
Temp
SELECT
Code, Name, NumberOfDebtors
FROM
#tmpDbrNo
WHERE
rownum BETWEEN #V_STARTS_WITH AND #V_STARTS_WITH + #V_NUMBER_OF_ROWS
END
Above query takes about 25 sec to execute which is too long to wait. And if I comment out the line where I have called the scalar function in the where clause, it takes 0 secs to execute the query.
Can anybody suggest better way which may take minimum secs to execute the query? I have tried to put call to function in case like as below, but no success.
AND 1 = CASE WHEN DBR_CLIENT='XXXXXX' THEN CheckClients(#V_USER_ID,DBR_NO,DBR_CLIENT) ELSE 1 END
This is just a shot in the dark because we were not provided with any ddl or much to work with. I think I interpreted the existing logic in your scalar function correctly. As a general rule you should probably avoid using flags. This is a very old school mindset and is not suited to relational data very well at all. I suspect this could be greatly improved with an understanding of the actual requirements but this is the best I could do with the limited details.
CREATE FUNCTION [CheckClients]
(
#UserId Varchar(3),
#DbrNo varchar(10),
#V_DBR_CLIENT varchar(6)
)
RETURNS table as return
with RowCounts as
(
select
(
select COUNT(DBR_CLIENT)
from DBR
where DBR_SERIES like #DbrNo + 'T'
) as ClientCount
,
(
select COUNT(USR_CLI)
from USRAGYCLI u
inner join DBR d on d.DBR_CLIENT = u.USR_CLI
where u.USR_CODE = #UserId
and d.DBR_SERIES like #DbrNo +'T'
) as UserCount
)
select case
when #V_DBR_CLIENT = 'XXXXXX' then
Case when rc.UserCount <> rc.ClientCount then 0
when rc.ClientCount < 0 then 0
else 1
end
else 1
end as Flag
from RowCounts rc
You can optimize your scalar function query to reduce doing multiple read. Like:
ALTER FUNCTION [CheckClients] (
#UserId VARCHAR(3),
#DbrNo VARCHAR(10),
#V_DBR_CLIENT VARCHAR(6)
)
RETURNS INT
AS
BEGIN
DECLARE #Flag INT
SET #Flag = 1
IF (#V_DBR_CLIENT = 'XXXXXX')
BEGIN
DECLARE #Count INT = ISNULL((
SELECT COUNT(DBR_CLIENT)
FROM DBR
WHERE DBR_SERIES LIKE #DbrNo + 'T'
), 0);
IF (
(ISNULL((
SELECT COUNT(USR_CLI)
FROM USRAGYCLI
INNER JOIN DBR ON DBR_CLIENT = USR_CLI
WHERE USR_CODE = #UserId
AND DBR_SERIES LIKE #DbrNo + 'T'
), 0) <> #Count)
OR (#Count <= 0)
)
BEGIN
SET #Flag = 0
END
END
RETURN #Flag
END
Also, you need to study your execution plan of the query to find out where the query is having high cost of execution time. And create non-clustered index if necessary.
-- EDITED LATER --
The non-Sargable Problem (Calling Scalar Function):
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [SEL_CLI]
#V_USER_ID VARCHAR(3),
#V_NUMBER_OF_ROWS INT,
#V_STARTS_WITH INT
AS
BEGIN
CREATE TABLE #tmpDbrNo
(
Code VARCHAR(10),
Name VARCHAR(100),
NumberOfDebtors int,
rownum int
)
;WITH Temp AS
(
SELECT
CLT_NO AS Code,
CLT_NAME AS Name,
COUNT(DBR_NO) AS NumberOfDebtors
FROM
DBR
JOIN
USRAGYCLI ON DBR_CLIENT = USR_AGY_CLI
JOIN
CLT ON DBR_CLIENT = CLT_NO
WHERE
USR_CODE = #V_USER_ID
AND 1 =
(CASE
WHEN (#V_DBR_CLIENT = 'XXXXXX') THEN
(CASE
WHEN (
ISNULL((
SELECT COUNT(USR_CLI)
FROM USRAGYCLI
INNER JOIN DBR ON DBR_CLIENT = USR_CLI
WHERE USR_CODE = #UserId
AND DBR_SERIES LIKE #DbrNo + 'T'
), 0) <> ISNULL((
SELECT COUNT(DBR_CLIENT)
FROM DBR
WHERE DBR_SERIES LIKE #DbrNo + 'T'
), 0)
)
OR (ISNULL((
SELECT COUNT(DBR_CLIENT)
FROM DBR
WHERE DBR_SERIES LIKE #DbrNo + 'T'
), 0) <= 0)
THEN 0
ELSE 1
END)
ELSE 1
END)--CheckClients(#V_USER_ID, DBR_NO, DBR_CLIENT)
GROUP BY
CLT_NO, CLT_NAME
)
INSERT INTO #tmpDbrNo
SELECT
Code, Name, NumberOfDebtors,
ROW_NUMBER() OVER (ORDER by Code) rownum
FROM
Temp
SELECT
Code, Name, NumberOfDebtors
FROM
#tmpDbrNo
WHERE
rownum BETWEEN #V_STARTS_WITH AND #V_STARTS_WITH + #V_NUMBER_OF_ROWS
END
As you can see, the scalar function can be included in the same query, but if you study the function nicely than it is clear that the query in scalar function is not fully dependent on the query in the store procedure. It is making count and will reread and manipulate the data from the table every time.
So, with this type of query making non-Sargable to Sargable will not improve the performance. The possible solution to the problem will be
To previously add the required data in the table and check from there.
To study your query plans(Design and Execution) and optimize it accordingly.

Using row count from a temporary table in a while loop SQL Server 2008

I'm trying to create a procedure in SQL Server 2008 that inserts data from a temp table into an already existing table. I think I've pretty much figured it out, I'm just having an issue with a loop. I need the row count from the temp table to determine when the loop should finish.
I've tried using ##ROWCOUNT in two different ways; using it by itself in the WHILE statement, and creating a variable to try and hold the value when the first loop has finished (see code below).
Neither of these methods have worked, and I'm now at a loss as to what to do. Is it possible to use ##ROWCOUNT in this situation, or is there another method that would work better?
CREATE PROCEDURE InsertData(#KeywordList varchar(max))
AS
BEGIN
--create temp table to hold words and weights
CREATE TABLE #tempKeywords(ID int NOT NULL, keyword varchar(10) NOT NULL);
DECLARE #K varchar(10), #Num int, #ID int
SET #KeywordList= LTRIM(RTRIM(#KeywordList))+ ','
SET #Num = CHARINDEX(',', #KeywordList, 1)
SET #ID = 0
--Parse varchar and split IDs by comma into temp table
IF REPLACE(#KeywordList, ',', '') <> ''
BEGIN
WHILE #Num > 0
BEGIN
SET #K= LTRIM(RTRIM(LEFT(#KeywordList, #Num - 1)))
SET #ID = #ID + 1
IF #K <> ''
BEGIN
INSERT INTO #tempKeywords VALUES (#ID, #K)
END
SET #KeywordList = RIGHT(#KeywordList, LEN(#KeywordList) - #Num)
SET #Num = CHARINDEX(',', #KeywordList, 1)
--rowcount of temp table
SET #rowcount = ##ROWCOUNT
END
END
--declaring variables for loop
DECLARE #count INT
DECLARE #t_name varchar(30)
DECLARE #key varchar(30)
DECLARE #key_weight DECIMAL(18,2)
--setting count to start from first keyword
SET #count = 2
--setting the topic name as the first row in temp table
SET #t_name = (Select keyword from #tempKeywords where ID = 1)
--loop to insert data from temp table into Keyword table
WHILE(#count < #rowcount)
BEGIN
SET #key = (SELECT keyword FROM #tempKeywords where ID = #count)
SET #key_weight = (SELECT keyword FROM #tempKeywords where ID = #count+2)
INSERT INTO Keyword(Topic_Name,Keyword,K_Weight)
VALUES(#t_name,#key,#key_weight)
SET #count= #count +2
END
--End stored procedure
END
To solve the second part of your problem:
INSERT INTO Keyword(Topic_Name,Keyword,K_Weight)
SELECT tk1.keyword, tk2.keyword, tk3.keyword
FROM
#tempKeywords tk1
cross join
#tempKeywords tk2
inner join
#tempKeywords tk3
on
tk2.ID = tk3.ID - 1
WHERE
tk1.ID = 1 AND
tk2.ID % 2 = 0
(This code should replace everything in your current script from the --declaring variables for loop comment onwards)
You could change:
WHILE(#count < #rowcount)
to
WHILE(#count < (select count(*) from #tempKeywords))
But like marc_s commented, you should be able to do this without a while loop.
I'd look at reworking your query to see if you can do this in a set based way rather than row by row.
I'm not sure I follow exactly what you are trying to achieve, but I'd be tempted to look at the ROW_NUMBER() function to set the ID of your temp table. Used with a recursive CTE such as shown in this answer you could get an id for each of your non empty trimmed words. An example is something like;
DECLARE #KeywordList varchar(max) = 'TEST,WORD, ,,,LIST, SOME , WITH, SPACES'
CREATE TABLE #tempKeywords(ID int NOT NULL, keyword varchar(10) NOT NULL)
;WITH kws (ord, DataItem, Data) AS(
SELECT CAST(1 AS INT), LEFT(#KeywordList, CHARINDEX(',',#KeywordList+',')-1) ,
STUFF(#KeywordList, 1, CHARINDEX(',',#KeywordList+','), '')
union all
select ord + 1, LEFT(Data, CHARINDEX(',',Data+',')-1),
STUFF(Data, 1, CHARINDEX(',',Data+','), '')
from kws
where Data > ''
), trimKws(ord1, trimkw) AS (
SELECT ord, RTRIM(LTRIM(DataItem))
FROM kws
)
INSERT INTO #tempKeywords (ID, keyword)
SELECT ROW_NUMBER() OVER (ORDER BY ord1) as OrderedWithoutSpaces, trimkw
FROM trimKws WHERE trimkw <> ''
SELECT * FROM #tempKeywords
I don't fully understand what you are trying to acheive with the second part of your query , but but you could just build on this to get the remainder of it working. It certainly looks as though you could do what you are after without while statements at least.

Sql script syntax and grammar issues

Can someone help please I dont know what I am doing wrong:
IF EXISTS ( SELECT name
FROM sys.tables
WHERE name = N'MemberIdsToDelete' )
DROP TABLE [MemberIdsToDelete];
GO
SELECT mm.memberid ,
mm.aspnetuserid ,
mm.email ,
mm.RowNum AS RowNum
INTO #MemberIdsToDelete
FROM membership.members AS mm
LEFT JOIN aspnet_membership AS asp ON mm.aspnetuserid = asp.userid
LEFT JOIN trade.tradesmen AS tr ON tr.memberid = mm.memberid
WHERE asp.isapproved = 0
AND tr.ImportDPN IS NOT NULL
AND tr.importDPN <> ''
ORDER BY mm.memberid
DECLARE #MaxRownum INT
SET #MaxRownum = ( SELECT MAX(RowNum)
FROM #MemberIdsToDelete
)
DECLARE #Iter INT
SET #Iter = ( SELECT MIN(RowNum)
FROM #MemberIdsToDelete
)
DECLARE #MemberId INT
DECLARE #TrademId INT
DECLARE #UId UNIQUEIDENTIFIER
DECLARE #Successful INT
DECLARE #OutputMessage VARCHAR(200)
DECLARE #Email VARCHAR(100)
DECLARE #Username VARCHAR(100)
SELECT #MemberId = memberId ,
#UId = AspNetUserId
FROM MemberIdsToDelete
SELECT #TrademId = TradesManId
FROM trade.TradesMen
WHERE memberId = #MemberId;
WHILE #Iter <= #MaxRownum
BEGIN
SELECT *
FROM #MemberIdsToDelete
WHERE RowNum = #Iter
--more code here
SET #Iter = #Iter + 1
END
I just want to check if my table MemberIdsToDelete exists, if so drop it,
create MemberIdsToDelete with the results set from the select
loop through MemberIdsToDelete table and perform operations
I am getting error that RowNum does not exist
For a start, to check if a table exists and then drop accordingly, you need to use something like
IF EXISTS (SELECT name
FROM sys.tables
WHERE name = N'MemberIdsToDelete')
DROP TABLE [MemberIdsToDelete];
GO
as for the error, your RowNum column does not exist when you are attempting to reference it. Include it in the SELECT statement
select mm.memberid, mm.aspnetuserid, mm.email, mm.RowNum AS RowNum
into #MemberIdsToDelete
from membership.members as mm
left join aspnet_membership as asp
on mm.aspnetuserid=asp.userid
left join trade.tradesmen as tr
on tr.memberid=mm.memberid
where asp.isapproved = 0 and tr.ImportDPN IS NOT NULL
and tr.importDPN <> ''
order by mm.memberid;
GO
I hope this helps.
Edit. Based on you additional error from your comment. You are now attempting to access a temporary table that does not exist. You must first populate the temporary table #MemberIdsToDelete before attempting to read from it. The invalid column error is down to the same problem. You are attempting to read a column called RowNum from the temporary table which does not exist.
Edit2. Remove the '#' from the #MemberIdsToDelete. You are inserting into a table not a temporary table. Or, Add a # to the select into above (see the code above). This will make it a temporary table as required.
You don't have a RowNum column in that table.
Try:
select mm.memberid, mm.aspnetuserid, mm.email, row_number() over (order by (select 1)) as RowNum
....
This should solve your problem, but I wouldnt actually recommend this idea of looping through the ones to delete.

SQL Server Full Text Search Very Slow

I have a stored procedure that searches a table which has about 200000+ rows with full text FREETEXT.
Here is the basics of it:
declare #searchKey varchar(150)
if #searchKey Is Null OR LEN(#searchKey)=0
Set #searchKey='""';
Set #searchKey='car';
declare #perPage int
Set #perPage=40
declare #pageNo int
Set #pageNo=1
declare #startIndex int,#endIndex int;
Set #startIndex=#perPage*#pageNo-#perPage+1;
Set #endIndex=#perPage*#pageNo;
Select totalItems
--i pull other colums as well
from (
Select Row_Number() over(order by CreateDate DESC) As rowNumber
,COUNT(*) OVER() as totalItems
--other columns are pulled as well
from MyTable P
Where
#searchKey='""'
OR FreeText((P.Title,P.Description),#searchKey)
) tempData
--where rowNumber>=#startIndex AND rowNumber<=#endIndex
where
rowNumber>=CASE WHEN #startIndex>0 AND #endIndex>0 THEN #startIndex ELSE rowNumber END
AND rowNumber<=CASE WHEN #startIndex>0 AND #endIndex>0 THEN #endIndex ELSE rowNumber END
order by rowNumber
The problem is its running slower then i would like it. Its taking about 3 seconds to load the page. Same page was loading in less then 1 sec when i was using like operator.
In my experience, full text index functions do not work well in a clause that contains an "OR" operator. I have had to get the same behavior by adjusting my query to use a UNION. Try this and see if you can get better performance.
declare #searchKey varchar(150)
if #searchKey Is Null OR LEN(#searchKey)=0
Set #searchKey='""';
Set #searchKey='car';
declare #perPage int
Set #perPage=40
declare #pageNo int
Set #pageNo=1
declare #startIndex int,#endIndex int;
Set #startIndex=#perPage*#pageNo-#perPage+1;
Set #endIndex=#perPage*#pageNo;
Select totalItems
--i pull other colums as well
from (
Select Row_Number() over(order by CreateDate DESC) As rowNumber
,COUNT(*) OVER() as totalItems
--other columns are pulled as well
from
(
select * from
MyTable A
Where
#searchKey='""'
UNION
select * from MyTable B
where FreeText((B.Title,B.Description),#searchKey)
) as innerTable
) tempData
--where rowNumber>=#startIndex AND rowNumber<=#endIndex
where
rowNumber>=CASE WHEN #startIndex>0 AND #endIndex>0 THEN #startIndex ELSE rowNumber END
AND rowNumber<=CASE WHEN #startIndex>0 AND #endIndex>0 THEN #endIndex ELSE rowNumber END
order by rowNumber

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