Procedure data input a temp table - sql-server

I have a procedure which returns the result correctly, but I could not store the procedure result in a single table or temptable.
My procedure return values like this
ItemName
BATH RUG-VDW.WO COTTON CLASSIC FRAME
-------------------------------------------
ItemName ItemCode StyleNo UPC EAN UnitFactor
BATH RUG-VDW.WO COTTON CLASSIC FRAME 4 884631844635 0884631844635 4.580
BATHRUG-VDW.WO COTTON CLASSIC FRAME 5 884631844659 0884631844659 4.580
BATH RUG-VDW.W COTTON CLASSIC FRAME 6 884631844666 0884631844666 4.580
ItemName
BATH RUG-VDW.WOVEN COTTON CLASSIC FRAME 12-840
-------------------------------------------
ItemName ItemCode StyleNo UPC EAN UnitFactor
BATH RUG-VDW.WOVEN COTTON CLASSIC FRAME 12-840 30 12-840 884631881906 0884631881906 4.580
BATH RUG-VDW.WOVEN COTTON CLASSIC FRAME 12-840 31 12-840 884631881913 0884631881913 4.580
----------------------------------------------------------------------------
Code:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[sp_GetDetail]
AS
BEGIN
SET NOCOUNT ON;
DECLARE #tab TABLE (no INT, name VARCHAR(30))
DECLARE #MyCursor CURSOR;
DECLARE #MyField VARCHAR(MAX);
DECLARE #Qry VARCHAR(MAX);
SET NOCOUNT ON;
SET #MyCursor = CURSOR FOR
SELECT DISTINCT ItemName FROM dbo.NewTest
SET #Qry=''
OPEN #MyCursor
FETCH NEXT FROM #MyCursor INTO #MyField
WHILE ##FETCH_STATUS = 0
BEGIN
/* YOUR ALGORITHM GOES HERE */
SET #Qry = #Qry + '; SELECT '''+#MyField+''' AS ItemName'
SET #Qry = #Qry + '; SELECT ItemName, ItemCode, StyleNo, UPC, EAN, UnitFactor FROM NewTest WHERE ItemName = '''+#MyField+''''
FETCH NEXT FROM #MyCursor INTO #MyField
END;
EXEC (#Qry);
CLOSE #MyCursor ;
DEALLOCATE #MyCursor;
END;

Cursors should be avoided as much as possible. This returns a single result set. Hope this is OK for your needs.
IF OBJECT_ID('tempdb..#MyTempTable') IS NOT NULL
DROP TABLE #MyTempTable;
CREATE TABLE #MyTempTable
(
ItemName NVARCHAR(100) ,
ItemCode NVARCHAR(100) ,
StyleNo NVARCHAR(100) ,
UPC NVARCHAR(100) ,
EAN NVARCHAR(100) ,
UnitFactor NVARCHAR(100) ,
ItemNameGroup NVARCHAR(100)
);
WITH cte
AS ( SELECT [ItemName] ,
[ItemCode] ,
[StyleNo] ,
[UPC] ,
[EAN] ,
[UnitFactor] ,
ROW_NUMBER() OVER ( PARTITION BY [ItemName] ORDER BY [ItemName] ) AS rn
FROM [dbo].[NewTest]
)
INSERT INTO [#MyTempTable]
( [ItemName] ,
[ItemCode] ,
[StyleNo] ,
[UPC] ,
[EAN] ,
[UnitFactor] ,
[ItemNameGroup]
)
SELECT c.[ItemName] ,
c.[ItemCode] ,
c.[StyleNo] ,
c.[UPC] ,
c.[EAN] ,
c.[UnitFactor] ,
t.[ItemName]
FROM [cte] AS c
INNER JOIN ( SELECT [cte].[ItemName]
FROM [cte]
WHERE [cte].[rn] = 1
) t ON c.[ItemName] = t.[ItemName];
SELECT [mtt].[ItemName] ,
[mtt].[ItemCode] ,
[mtt].[StyleNo] ,
[mtt].[UPC] ,
[mtt].[EAN] ,
[mtt].[UnitFactor] ,
[mtt].[ItemNameGroup]
FROM [#MyTempTable] AS [mtt];

If you want the dynamic sql to save values into a temp table, create the temp table first (before the cursor),
IF OBJECT_ID('tempdb..#MyTempTable') IS NOT NULL DROP TABLE #MyTempTable
create table #MyTempTable (ItemName varchar(100), ItemCode varchar(100), StyleNo varchar(100), UPC varchar(100), EAN varchar(100),
UnitFactor varchar(100)) -- change these definitions to match your actual data
then change the code which builds the sql:
Set #Qry=#Qry+'; select '''+#MyField+''' as ItemName'
Set #Qry=#Qry+'; insert into #MyTempTable select ItemName, ItemCode, StyleNo, UPC, EAN,
UnitFactor from NewTest where ItemName ='''+#MyField+''''
the temp table #MyTempTable will be available outside the cursor:
select * from #MyTempTable
drop table #MyTempTable

You return 2 tables, and they have a different structure. You can't store that in a temp table.
Why do you do that first SELECT of just the name anyway? It seems completely redundant.

Related

Adventure Works 2019: Adding New Person to Person.Person Table

Right now I am using 3 stored procedures to Add a person to the person.person table. I would like to cut this down to a single stored procedure to resolve this issue.
INSERT new GUID and DateModified to table Person.BusinessEntity
SELECT the auto generate BusinessEntityID form table Person.BusinessEntity
INSERT new Person to Person.Person Table
The Stored Procedures all use parameters which I pass via a C# application and I have confirmed that the a user is in fact added to the AdventureWorks2019 Db.
Procedure: Person.CreateNewBusinessEntity
INSERT INTO [Person].[BusinessEntity]
(
[BusinessEntity].rowguid
, [BusinessEntity].ModifiedDate
)
VALUES
(
#RowGUID
, GetDate()
)
Procedure: Person.GetBusinessEntityID
SELECT
[BusinessEntityID]
FROM
[AdventureWorks2019].[Person].[BusinessEntity]
WHERE [rowguid] = #RowGuid
Procedure: Person.CreateNewPerson
INSERT INTO [Person].[Person]
(
[BusinessEntityID]
,[PersonType]
,[NameStyle]
,[Title]
,[FirstName]
,[MiddleName]
,[LastName]
,[Suffix]
,[EmailPromotion]
,[AdditionalContactInfo]
,[Demographics]
,[rowguid]
,[ModifiedDate]
)
VALUES
(
#BusinessEntityID
, #PersonType
, #NameStyle
, #Title
, #FirstName
, #MiddleName
, #LastName
, #Suffix
, #EmailPromotion
, #AdditionalContactInfo
, #Demographics
, #RowGUID
, GetDate()
)
Any help here is appreciated. Thanks!
Thanks to HABO, I am now using this solution. Now I only need two procedures.
DECLARE #Inserted table ( [BusinessEntityID] int );
INSERT INTO [Person].[BusinessEntity]
(
[BusinessEntity].rowguid
, [BusinessEntity].ModifiedDate
)
OUTPUT inserted.[BusinessEntityID] INTO #Inserted([BusinessEntityID])
VALUES
(
#RowGUID
, GetDate()
)
The below answer shows you how to get the inserted id and add it to the next insert in side the Same SP.
USE AdventureWorks2012
GO
CREATE PROC CreateNewPerson
AS
BEGIN
DECLARE #OutputTbl TABLE ([BusinessEntityID] INT, ModifiedDate DATETIME)
DECLARE #BusinessEntityID AS INT
INSERT INTO [Person].[BusinessEntity]
(
[BusinessEntity].rowguid
, [BusinessEntity].ModifiedDate
)
--Get the output value inserted to a table.
OUTPUT inserted.[BusinessEntityID], inserted.ModifiedDate INTO
#OutputTbl([BusinessEntityID],[ModifiedDate])
VALUES
(
NEWID()
, GetDate()
)
--Assigned to a variable. You can get this using subquery as well inside the insert statment.
SELECT #BusinessEntityID = [BusinessEntityID] FROM #OutputTbl
INSERT INTO [Person].[Person]
(
[BusinessEntityID]
,[PersonType]
,[NameStyle]
,[Title]
,[FirstName]
,[MiddleName]
,[LastName]
,[Suffix]
,[EmailPromotion]
,[AdditionalContactInfo]
,[Demographics]
,[rowguid]
,[ModifiedDate]
)
VALUES
(
#BusinessEntityID
, #PersonType --These columns with # sign needed to be declared or supply values
, #NameStyle
, #Title
, #FirstName
, #MiddleName
, #LastName
, #Suffix
, #EmailPromotion
, #AdditionalContactInfo
, #Demographics
, NEWID() --This will generate a new GUID for each row.
, GetDate()
)
END
GO
Second way is using Scope_identity(). Replace the
"SELECT #BusinessEntityID = [BusinessEntityID] FROM #OutputTbl'
from below lines in the Sp will do the same thing for you.
SELECT #BusinessEntityID = SCOPE_IDENTITY() --[BusinessEntityID] FROM #OutputTbl
Select #BusinessEntityID

Cursor poor performance issue

I have a query which runs very slow. After some observations, I feel that it is because of using cursor in the query.
CREATE PROCEDURE [dbo].[spTest] (#INwww varchar(6)) AS
declare #curwwwbegdate datetime
declare #prevwwwbegdate datetime
declare #PrevwwwNum varchar(6)
set NOCOUNT ON
set #CurwwwBegDate = (select Begindate from wwwNUM where wwwnumber = #Inwww)
set #PrevwwwNum = (SELECT TOP 1 wwwNUMBER from wwwNUM WHERE BEGINDATE < #curwwwbegdate
order by begindate desc)
CREATE TABLE #ContRevExp (Inum int, lwww varchar(6),BookNum varchar(13), ldate datetime, lllLoc varchar(8), currentrev varchar(20), currentexp varchar(20), oldrev varchar(20), oldexp varchar(20), ContFileLoc varchar(100), bookloc varchar(3))
create table #wwwdates (wdLoc varchar(3),wdwww varchar(6), wdBegDate datetime, wdEndDate datetime)
create table #LocTable (loc varchar(3))
insert into #LocTable
SELECT LocCode from Location WHERE STATUS = 1 AND CCC = 1
DECLARE LocCursor INSENSITIVE CURSOR FOR
select * from #LocTable
OPEN LocCursor
declare #Loc varchar(3)
FETCH NEXT FROM LocCursor into #Loc
WHILE (##FETCH_STATUS = 0)
BEGIN
insert into #wwwdates (wdLoc, wdwww, wdBegDate, wdEndDate)
select #Loc,#Inwww,
(select top 1 wwwllldate from cccswwwllled where lllwww = #PrevwwwNum and branch = #Loc),
(select top 1 wwwllldate from cccswwwllled where lllwww = #Inwww and branch = #Loc)
FETCH NEXT FROM LocCursor into #Loc
END
deallocate LocCursor
Drop table #LocTable
DECLARE LocwwwCursor INSENSITIVE CURSOR FOR
select wdLoc, wdBegDate, wdEndDate from #wwwdates
declare #INFILELOC as varchar(3)
declare #INBEGDATE datetime
declare #INENDDATE datetime
OPEN LocwwwCursor
FETCH NEXT FROM LocwwwCursor into #INFILELOC, #INBEGDATE, #INENDDATE
WHILE (##FETCH_STATUS = 0)
BEGIN
declare #INVNUM int
declare #INDATE datetime
create table #cccs (cInvNum int)
insert into #cccs
Select distinct cccInv_Num from cccswwwllled
left join cccsexport on cccsExport.inv_num = cccinv_num
where cccswwwllled.Branch = #INFILELOC
and (cccswwwllled.impexp <> 1 or cccswwwllled.impexp is null)
and lllwww < #INwww and ContBookIndicator = 'C'
and cccsexport.lastupdate >= #INBEGDATE and cccsexport.lastupdate < #INENDDATE
DECLARE cccCursor INSENSITIVE CURSOR FOR
select * from #cccs
OPEN cccCursor
declare #cnum int
FETCH NEXT FROM cccCursor into #cnum
WHILE (##FETCH_STATUS = 0)
BEGIN
insert into #ContRevExp (Inum , lwww ,BookNum, ldate , lllloc , currentrev , currentexp, oldrev, oldexp, contfileloc, bookloc )
select top 1 cccInv_Num, lllwww,
(Select top 1 cccNumber from cccsExport where inv_num = #cnum and lastupdate <= #INENDDATE order by lastupdate desc ),
wwwlllDate,CE.FileNum,
0,
--case when CE.STATUS = 0 then '0' else convert(varchar(20),CE.TotalCost + CE.AllinRpoCost) end,
case when CE.STATUS = 0 then '0' else convert(varchar(20),CE.TotalExpense) end,
0,
--(Select top 1 convert(varchar(20),TotalCost + Allinrpocost) from cccsExport where inv_num = #cnum and lastupdate <= #INBEGDATE order by lastupdate desc ),
(Select top 1 convert(varchar(20),TotalExpense) from cccsExport where inv_num = #cnum and lastupdate <= #INBEGDATE order by lastupdate desc ),
'Cont/File/Loc',
BookLocation from cccswwwllled as CWL
left join cccsexport as CE on CE.inv_num = CWL.cccInv_Num
where CWL.cccInv_Num = #cnum
and CWL.lllwww < #INwww and CWL.ContBookIndicator = 'C'
and CE.lastupdate >= #INBEGDATE and CE.lastupdate <= #INENDDATE
order by CE.lastupdate desc
FETCH NEXT FROM cccCursor into #cnum
END
deallocate cccCursor
drop table #cccs
FETCH NEXT FROM LocwwwCursor into #INFILELOC, #INBEGDATE, #INENDDATE
END
deallocate LocwwwCursor
drop table #wwwdates
select bookloc,lwww,booknum,Inum,oldrev,oldexp,currentrev,currentexp,lllloc,
case when contfileloc is null then 'NOT' ELSE contfileloc end as 'Cont' from #ContRevExp
where (currentrev <> oldrev) or (currentexp <> oldexp)
order by bookloc, booknum
drop table #ContRevExp
I am not strong on cursor. My question is how to modify the code to improve the performance?
EDIT
The real problem is that I use ado.net to retrieve the data in a web application, the entire time to download data from server to the client is about 10 seconds, By using telerik justtrace tool, I found the stored procedure spent 99% time.
In the query, I found there is a nest cursors usage. cccCursor and LocwwwCursor. It means that there are two while loops, one is inside the other one. I guess that is the main reason, so I hope to replace the cursors with other skills.
SAMPLE DATA:
Because there are a lot of tables. I try to make the logic clear. We get the data from table Location:
The temp table #LocTable only has one column. The sample data is
AAA
BBB
CCC
DDD
EEE
FFF
GGG
I believe LocCursor is dealing each value of above data.
The input of the stored procedure is Inwww, let say it is 200218.
We have the query set #CurwwwBegDate = (select Begindate from wwwNUM where wwwnumber = #Inwww) will return a datetime 2002-04-28 00:00:00.000.
Another query set #PrevwwwNum = (SELECT TOP 1 wwwNUMBER from wwwNUM WHERE BEGINDATE < #curwwwbegdate
order by begindate desc) will return 200217. The temp table #wwwdates has the sample data as below.
wdLoc wdWeek wdBegDate wdEndDate
AAA 200218 NULL NULL
BBB 200218 NULL NULL
CCC 200218 NULL NULL
DDD 200218 2002-05-06 13:39:31.000 2002-05-07 16:52:44.000
EEE 200218 NULL NULL
FFF 200218 2002-05-06 13:39:40.000 2002-05-07 16:53:42.000
GGG 200218 NULL NULL
I think that the cursor LocwwwCursor takes the value from the temp table #wwwdatae then insert #cccs. I haven't got the sample data yet.
The final result likes(I just demonstrate it with one row)
ARL ARL 200510 IETARL0510087 150547816 $0.00 155.21 $0.00 155.00 ARL SOMETHING
There are unnecessary loops for aggerates in the SP above. Most of your logic could be condensed and made more performant with grouping and aggregations. For example, The whole block below could be drastically simplified.
REPLACE THIS ....
declare #prevwwwbegdate datetime
declare #PrevwwwNum varchar(6)
set NOCOUNT ON
set #CurwwwBegDate = (select Begindate from wwwNUM where wwwnumber = #Inwww)
set #PrevwwwNum = (SELECT TOP 1 wwwNUMBER from wwwNUM WHERE BEGINDATE < #curwwwbegdate
order by begindate desc)
CREATE TABLE #ContRevExp (Inum int, lwww varchar(6),BookNum varchar(13), ldate datetime, lllLoc varchar(8), currentrev varchar(20), currentexp varchar(20), oldrev varchar(20), oldexp varchar(20), ContFileLoc varchar(100), bookloc varchar(3))
create table #wwwdates (wdLoc varchar(3),wdwww varchar(6), wdBegDate datetime, wdEndDate datetime)
create table #LocTable (loc varchar(3))
insert into #LocTable
SELECT LocCode from Location WHERE STATUS = 1 AND CCC = 1
DECLARE LocCursor INSENSITIVE CURSOR FOR
select * from #LocTable
OPEN LocCursor
declare #Loc varchar(3)
FETCH NEXT FROM LocCursor into #Loc
WHILE (##FETCH_STATUS = 0)
BEGIN
insert into #wwwdates (wdLoc, wdwww, wdBegDate, wdEndDate)
select #Loc,#Inwww,
(select top 1 wwwllldate from cccswwwllled where lllwww = #PrevwwwNum and branch = #Loc),
(select top 1 wwwllldate from cccswwwllled where lllwww = #Inwww and branch = #Loc)
FETCH NEXT FROM LocCursor into #Loc
END
deallocate LocCursor
Drop table #LocTable
WITH THIS ....
create table #wwwdates (wdLoc varchar(3),wdwww varchar(6), wdBegDate datetime, wdEndDate datetime)
insert into #wwwdates
SELECT
LocCode,#Inwww,MAX(Prev_MaxDate),MAX(In_MaxDate)
FROM
(
SELECT LocCode,
Prev_MaxDate=CASE WHEN cccswwwllled.lllwww=#PrevwwwNum THEN wwwllldate ELSE NULL END,
In_MaxDate=CASE WHEN cccswwwllled.lllwww=#Inwww THEN wwwllldate ELSE NULL END
from
Location
INNER JOIN cccswwwllled ON cccswwwllled.lllwww IN(#PrevwwwNum,#Inwww) AND cccswwwllled.Branch=Location.LocCode
WHERE Location.STATUS = 1 AND Location.CCC = 1
)AS X
GROUP BY
LocCode
You should remove cursors and create set-based solution. You and mssql engine are not strong in cursor solutions :).
Rething what you would like to do. Join wwwNUM + lllwww + Location into view something like + check indexes on wwwnumber, branch, LocCode + STATUS + CCC (depends on data types)
create view MyViewWithNameWhichDescriptContain
as
select
l.LocCode,
n.wwwNUMBER Inwww,
n.Begindate InwwwDate,
c.wwwllldate Inwww
from Location l, wwwNUM n, cccswwwllled c
where l.[STATUS] = 1 AND l.CCC = 1
and l.LocCode = c.branch
and c.lllwww = n.wwwnumber
select from this view:
if object_id('tempdb..#wwwMyDatesPerLocCode') is not null drop table #wwwMyDatesPerLocCode
select *
into #wwwMyDatesPerLocCode
from (
select *, row_number() over(partition by Inwww order by InwwwDate desc OrbyCol
from MyViewWithNameWhichDescriptContain) t
where OrbyCol in (1,2)
create view cccswwwllled + cccsexport lets say llledExported
make selfjoin to access descendant (wwwMyDatesPerLocCode where OrbyCol = 1 + wwwMyDatesPerLocCode where OrbyCol = 2) and connect with view llledExported to get your info with your filters
check indexes on all tables again
Finnaly you don't need variables and cursors. Try think in joins. If you will add some somple data from all tables and add description of your indexes I can make it more detailed.

How to write this procedure to get auto-increment alphanumeric id in SQL Server?

Table structure is as follows:
CREATE TABLE tblContact
(
SrNo DECIMAL IDENTITY(1,1) NOT NULL,
InquiryId VARCHAR(10) PRIMARY KEY,
SenderName VARCHAR(50),
SenderEmail VARCHAR(200),
SenderSubject VARCHAR(50),
SenderMessage VARCHAR(MAX),
IsActive BIT DEFAULT(1),
IsDelete BIT DEFAULT(0),
CreatedOn DATETIME DEFAULT(GETDATE()),
CreatedBy VARCHAR(10),
UpdatedOn DATETIME,
UpdatedBy VARCHAR(10)
)
Procedure listing is as follows:
CREATE PROC Usp_GetNewInquiryId
AS
BEGIN
IF NOT EXISTS(SELECT InquiryId FROM JobPortal.dbo.tblContact)
DECLARE #PrefixValue VARCHAR(10) = 'INQ'
DECLARE #InitialValue DECIMAL(10) = 1001
SELECT InquiryId = #PrefixValue + CAST(ISNULL(MAX(InquiryId), #InitialValue) AS VARCHAR(10))
FROM JobPortal.dbo.tblContact
ELSE
/* here I want to eliminate the word 'INQ' from 'INQ1001' towards left side and do increment 1002 from 1001, lastly want to select INQ1002 and so on... */
SELECT TOP 1
InquiryId = #PrefixValue + CONVERT(VARCHAR(10), SUBSTRING(InquiryId, 4, 4)) + 1
FROM JobPortal.dbo.tblContact
ORDER BY InquiryId DESC
END
Desired Output:
If table is empty then InquiryId=INQ1001
Otherwise InquiryId=INQ1002
If you want to return the "next available" InquiryId for tblContact, I would do this:
CREATE PROCEDURE Usp_GetNewInquiryId
AS
BEGIN
IF NOT EXISTS (SELECT InquiryId FROM tblContact)
SELECT 'INQ1001'
ELSE
SELECT TOP 1 'INQ' + CONVERT(VARCHAR,CONVERT(INT,REPLACE(InquiryId,'INQ','')) + 1) FROM tblContact ORDER BY InquiryId DESC
END

Cursors are taking time on my Stored Procedure?

I Have two cursors in stored procedure under SQL Server 2008 r2.They are killing my time when stored proc is executed?
i have 2 tables
##TEMP
CatalogID Value
34567 80
34848 100
34725 40
##Temp1
Name Percentage
A 25
B 25
C 25
D 25
By using both temp tables iam inserting data in to catalog table and deleting entered catalogid and value.
catalogtable
CatalogID name value
34567 A 20
34567 B 20
34567 C 20
34567 D 20
34848 A 25
34848 B 25
34848 C 25
34848 D 25
34725 A 10
34725 B 10
34725 C 10
34725 D 10
My Cursor is
DECLARE Cur_Rotation CURSOR
FOR
SELECT CatalogId,Value FROM ##TEMP
DECLARE #CatalogId INT
DECLARE #Value [decimal](5, 2)
OPEN Cur_Rotation
FETCH NEXT FROM Cur_Rotation INTO #CatalogId,#Value
While ##FETCH_STATUS = 0
BEGIN
DECLARE Cur_Inner CURSOR
FOR
SELECT Name,Percentage FROM ##Temp1
DECLARE #Name VARCHAR(50)
DECLARE #Percentage [decimal](5, 2)
OPEN Cur_Inner
FETCH NEXT FROM Cur_Inner INTO #Name,#Percentage
While ##FETCH_STATUS = 0
BEGIN
DECLARE #Value1 [decimal](5, 2)
SET #Value=#Value1*(#Percentage/100.00)
DELETE FROM CatalogDetails WHERE CatalogId=#CatalogId and name=#name
INSERT INTO CatalogDetails (name,RDDRotPcent,CatalogId)
VALUES (#name,#Value1,#CatalogId)
FETCH NEXT FROM Cur_Inner INTO #Name,#Percentage
END
CLOSE Cur_Inner
DEALLOCATE Cur_Inner
FETCH NEXT FROM Cur_Rotation INTO #CatalogId,#Value
END
CLOSE Cur_Rotation
DEALLOCATE Cur_Rotation
END
Is there any chance to use any logic to skip cursors .CatalogID will be thousands at that time my query execution is taking time.So is there any chance to change my SCRIPT to avoid cursors.
In your case you can do this without cursors and even without recursive queries:
insert into catalogtable (CatalogID, name, value)
select
t.CatalogID,
t1.name,
t.Value * t1.Percentage / 100.00
from #TEMP as t
cross join #TEMP1 as t1
sql fiddle demo
THis works and adresses your entire question I believe
--Prepare data
DECLARE #Temp1 AS TABLE
(
CatalogId INT,
VALUE DECIMAL(19,5)
)
INSERT INTO #Temp1 SELECT 34567, 80
INSERT INTO #Temp1 SELECT 34848, 100
INSERT INTO #Temp1 SELECT 34725, 40
DECLARE #CatalogDetails AS TABLE
(Id INT PRIMARY KEY IDENTITY(1,1), NAME NVARCHAR(100), RDDRotPcent DECIMAL(19,5), CatalogId INT)
INSERT INTO #CatalogDetails SELECT 'A', .99, 12345
INSERT INTO #CatalogDetails SELECT 'B', .99, 34567
DECLARE #Temp2 AS TABLE
( NAME NVARCHAR(100),
Percentage DECIMAL(19,5))
INSERT INTO #Temp2 SELECT 'A', .25
INSERT INTO #Temp2 SELECT 'B', .25
INSERT INTO #Temp2 SELECT 'C', .25
INSERT INTO #Temp2 SELECT 'D', .25
DECLARE #Catalog AS TABLE
(CatalogId INT, NAME NVARCHAR(100), VALUE DECIMAL(19,5))
--Fill Catalog with new Data
INSERT INTO #Catalog
SELECT CatalogId, NAME, Value * Percentage FROM #Temp1, #Temp2
--Delete Old Values
DELETE FROM #CatalogDetails WHERE Id IN (SELECT Id FROM #CatalogDetails CD Inner JOIN #Catalog C ON CD.CatalogId = C.CatalogId AND CD.Name = C.NAME)
--Insert New Values
INSERT INTO #CatalogDetails SELECT Name, Value, CatalogId FROM #Catalog
--View End Result
SELECT * FROM #CatalogDetails
Not very sure how much it is helpful. But you may try something like this to avoid cursor:-
DECLARE #number_rows int
DECLARE #count int
DECLARE #selected int
DECLARE #table1 TABLE (Id int not null primary key identity(1,1), col1 int )
INSERT into #table1 (col1) SELECT col1 FROM table2
SET #number_rows=##ROWCOUNT
SET #count=0
WHILE #count<#number_rows
BEGIN
SET #count=#count+1
SELECT
#selected=col1
FROM #table1
WHERE Id=#count
--do your stuff here--
END

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