I have two cursors, I want to loop through first one, and use its values as parameters for the second cursor and then do some processing. I know how to do this in PL-SQL, but T-SQL puzzles me.
I have gotten so far. For some reason the nested cursor does not print anything.
DECLARE #period DATE
DECLARE #table_type VARCHAR(2)
DECLARE #clmn_clr VARCHAR(100)
DECLARE #clmn_per VARCHAR(100)
DECLARE #period_num INT
DECLARE #period_date DATE
DECLARE #table_type2 VARCHAR(2)
DECLARE c_table CURSOR FOR SELECT period,
table_type
FROM #period_table
DECLARE c_period CURSOR LOCAL FAST_FORWARD FOR SELECT clmn_clr,
clmn_per,
period_num,
period_date,
table_type
FROM #column_period
WHERE table_type = #table_type
OPEN c_table
FETCH NEXT FROM c_table
INTO #period, #table_type
WHILE ##FETCH_STATUS = 0
BEGIN
print #period
print #table_type
print '------------'
FETCH NEXT FROM c_table
INTO #period, #table_type
--Nested cursor
OPEN c_period
FETCH NEXT FROM c_period
INTO #clmn_clr, #clmn_per, #period_num, #period_date, #table_type2
BEGIN
print #clmn_clr
print #clmn_per
print #period_num
print #period_date
print #table_type2
FETCH NEXT FROM c_period
INTO #clmn_clr, #clmn_per, #period_num, #period_date, #table_type2
END
CLOSE c_period
DEALLOCATE c_period
END
CLOSE c_table
DEALLOCATE c_table
It does print the first line from cursor c_table but that is all.
Also, any comments for how to do this in T-SQL better are very welcomed.
There is no need in nesting, here is the only cursor that can do everything:
DECLARE c_period CURSOR LOCAL FAST_FORWARD FOR
SELECT p.period,
c.clmn_clr,
c.clmn_per,
c.period_num,
c.period_date,
c.table_type
FROM #period_table p
INNER JOIN #column_period c
ON c.table_type = p.table_type
ORDER BY p.table_type
I never tested it but id does not seem to me that MSSQL allows something like "dynamic parameter binding" for cursor. Since you opened it - it's done. It only can react on data modifications if appripriate options applied.
You may reopen cursor if you still need nesting for any purpose. Fetch #table_type from outer cursor, then DECLARE ... OPEN nested cursor.
Also note that strings to print (if this is your real subject) can be obtained with single select, withot any cursors (search "string aggregation" for mssql).
Here is working solution I have found - I only moved the nested cursor inside.
DECLARE #period DATE
DECLARE #table_type VARCHAR(2)
DECLARE #clmn_clr VARCHAR(100)
DECLARE #clmn_per VARCHAR(100)
DECLARE #period_num INT
DECLARE #period_date DATE
DECLARE #table_type2 VARCHAR(2)
DECLARE c_table CURSOR FOR SELECT period,
table_type
FROM #period_table
--Fetch first row
OPEN c_table
FETCH NEXT FROM c_table
INTO #period, #table_type
--Loop starts here, until it is empty
WHILE ##FETCH_STATUS = 0
BEGIN
print #period
print #table_type
print '----------'
--Nested cursor
DECLARE c_period CURSOR LOCAL FAST_FORWARD FOR SELECT clmn_clr,
clmn_per,
period_num,
period_date,
table_type
FROM #column_period
WHERE table_type = #table_type
OPEN c_period
FETCH NEXT FROM c_period
INTO #clmn_clr, #clmn_per, #period_num, #period_date, #table_type2
WHILE ##FETCH_STATUS = 0
BEGIN
print #clmn_clr
print #clmn_per
print #period_num
print #period_date
print #table_type2
FETCH NEXT FROM c_period
INTO #clmn_clr, #clmn_per, #period_num, #period_date, #table_type2
END
CLOSE c_period
DEALLOCATE c_period
FETCH NEXT FROM c_table
INTO #period, #table_type
END
CLOSE c_table
DEALLOCATE c_table
Related
How do I select multiple columns in a cursor?
I tested the below code but it's returning/printing nothing.
DECLARE #DateAdded VARCHAR(50)
DECLARE #IdEmployee NVARCHAR(20)
DECLARE #EmailAddress VARCHAR(100)
DECLARE #Subject VARCHAR(50)
DECLARE #Message VARCHAR(100)
DECLARE #DateSent VARCHAR(50)
-- 2 - Declare Cursor
DECLARE db_cursor CURSOR FOR
-- Populate the cursor with your logic
-- * UPDATE WITH YOUR SPECIFIC CODE HERE *
SELECT
#DateAdded, #IdEmployee, #EmailAddress,
#Subject, #Message, #DateSent
FROM #tblLeaveNotifToApprover
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #DateAdded, #IdEmployee, #EmailAddress, #Subject, #Message, #DateSent
WHILE ##FETCH_STATUS = 0
BEGIN
-- 4 - Begin the custom business logic
-- * UPDATE WITH YOUR SPECIFIC CODE HERE *
PRINT CONVERT(VARCHAR,#DateAdded)
FETCH NEXT FROM db_cursor INTO #DateAdded, # IdEmployee, #EmailAddress, #Subject, #Message, #DateSent
END
CLOSE db_cursor
DEALLOCATE db_cursor
Initially wanted to print all that columns in each row, but after running the snippet/code it nothing displays, I also tried converting the variables to varchar same result is thrown.
I expect that all the columns of my 7 rows will be print out.
This is wrong
DECLARE db_cursor CURSOR FOR
-- Populate the cursor with your logic
-- * UPDATE WITH YOUR SPECIFIC CODE HERE *
SELECT
#DateAdded, #IdEmployee, #EmailAddress,
#Subject, #Message, #DateSent
FROM #tblLeaveNotifToApprover
You need to replace the variables in this SELECT statement with the actual column names from the table. You don't refer to your local variables until the FETCH statement.
Think of your CURSOR as a selected set of data from the source tables that you will then iterate through.
Your snippet contains the hints to identify what is in the CURSOR, in your case refer to
-- Populate the cursor with your logic
-- * UPDATE WITH YOUR SPECIFIC CODE HERE *
In your case, your are trying to populate your CURSOR using the values from the variables you have declared using the statement:
SELECT #DateAdded,
#IdEmployee,
#EmailAddress,
#Subject,
#Message,
#DateSent
FROM #tblLeaveNotifToApprover
Now, since you have not populated those variables yet they are all NULL
What you then do is SELECT the variables that you have just declared back into the variables !!!!!
FETCH NEXT FROM db_cursor INTO #DateAdded,#IdEmployee,#EmailAddress,#Subject,#Message,#DateSent
WHILE ##FETCH_STATUS = 0
So it is little surprise that when you try to print these you get nothing returned.
Go back to your DECLARE CURSOR and ensure that you are actually selecting a data set to iterate through. a CURSOR should generally always be kind of like a temporary table. If we assume that your temp table #tblLeaveNotifToApprover has column names that match your variable names then you need:
DECLARE db_cursor CURSOR FOR
SELECT DateAdded,
IdEmployee,
EmailAddress,
Subject,
Message,
DateSent
FROM #tblLeaveNotifToApprover
I have a table(Lets say'A') that contains the list of all tables in a database.I have defined a cursor that iterate through the tables name in 'A'.I want to update the table column defined in a cursor.
I have created 2 cursors.One to iterate over the tables names and other one to iterate over the column names.
DECLARE #MyCursor CURSOR;
DECLARE #MyField nvarchar(255);
BEGIN
SET #MyCursor = CURSOR FOR
select distinct Table_name from DataTable where Data <>'No'
set #a=0
OPEN #MyCursor
FETCH NEXT FROM #MyCursor
INTO #MyField
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #column_cursor CURSOR;
DECLARE #columnField nvarchar(255);
SET #column_cursor = CURSOR FOR
select Column_name from DataTable where TABLE_NAME=#MyField and Data
<>'No'
OPEN #column_cursor
FETCH NEXT FROM #column_cursor
INTO #columnField
WHILE ##FETCH_STATUS = 0
BEGIN
update #MyField set #columnField=''+#MyField+'_'+#columnField+#a
FETCH NEXT FROM #column_cursor
INTO #columnField
END;
CLOSE #column_cursor ;
DEALLOCATE #column_cursor;
FETCH NEXT FROM #MyCursor
INTO #MyField
END;
CLOSE #MyCursor ;
DEALLOCATE #MyCursor;
END;
Here in the update statement #MyField is giving error :"Must declare the table variable #MyField".
You are on good way but you can't use variable on table name place , you need to build your update command in variable and then execute dynamic sql.
You can see: Creating a dynamic sql query
in your case someting like
declare #sqlCommand varchar(max)
declare #MyField varchar(255) = 'table_name'
declare #columnField varchar(255) = 'column_NAME'
declare #columnValue varchar(255) = 'column_value'
set #sqlCommand = 'update '+ #MyField +' set '+#columnField+' = ' +#columnValue+ ' where 1=1;'
--select #sqlCommand
EXEC (#sqlCommand)
I have written this procedure,
DECLARE CURS_TABLE CURSOR FOR
SELECT NAME FROM SYS.TABLES WHERE NAME LIKE 'AK_LIB_ADDRESS'
DECLARE #TABLE_NAME VARCHAR(100);
DECLARE #SQL VARCHAR(300);
OPEN CURS_TABLE
FETCH NEXT FROM CURS_TABLE INTO #TABLE_NAME;
WHILE ##FETCH_STATUS = 0
BEGIN
SET #SQL = 'ALTER TABLE '+#TABLE_NAME+' ADD PRIMARY KEY(ID);';
EXEC (#SQL)
FETCH NEXT FROM CURS_TABLE INTO #TABLE_NAME
END
CLOSE CURS_TABLE;
DEALLOCATE CURS_TABLE;
The problem is the exec(#sql) executing the same statement twice, i checked by placing print statement,it's working fine with print statement if i comment exec line...
So please can u give me any idea where i'm doing wrong..?
You should do like this, used a temporary table instead of selecting directly from the sys.tables
SELECT NAME into #temp FROM SYS.TABLES WHERE NAME LIKE 'AK_LIB_ADDRESS%'
DECLARE CURS_TABLE CURSOR FOR
SELECT NAME from #temp
DECLARE #TABLE_NAME VARCHAR(100);
DECLARE #SQL VARCHAR(300);
OPEN CURS_TABLE
FETCH NEXT FROM CURS_TABLE INTO #TABLE_NAME;
WHILE ##FETCH_STATUS = 0
BEGIN
select #TABLE_NAME
SET #SQL = 'ALTER TABLE '+#TABLE_NAME+' ADD PRIMARY KEY(ID);';
EXEC (#SQL)
FETCH NEXT FROM CURS_TABLE INTO #TABLE_NAME
END
CLOSE CURS_TABLE;
DEALLOCATE CURS_TABLE;
Or add a Insensitive keyword to the cursor. The reason is that you are changing a heap to the B-tree inside the trigger, that's the reason you are getting inconsistent behaviour in the cursor.
DECLARE CURS_TABLE CURSOR INSENSITIVE FOR
SELECT NAME FROM SYS.TABLES WHERE NAME LIKE 'AK_LIB_ADDRESS%'
DECLARE #TABLE_NAME VARCHAR(100);
DECLARE #SQL VARCHAR(300);
I have the following table
Create table TestScene(
string varchar(60))
insert into TestScene values ('t_Scene');
insert into TestScene values ('v_Scene');
On this table,I want to parse each string and I'm doing that,the problem is that it's doubling the last field and I don't know why.Do you see what I'm missing?
DECLARE #contor int
DECLARE #stringut varchar(50)
DECLARE cursorName CURSOR -- Declare cursor
FOR
Select string FROM TestScene
Select #contor=count(*) from TestScene
OPEN cursorName -- open the cursor
print #contor
FETCH NEXT FROM cursorName INTO #stringut
PRINT #stringut+' ' -- print the name
WHILE #contor>0
BEGIN
FETCH NEXT FROM cursorName INTO #stringut
declare #i int,#j int
set #j=charindex(#stringut,'_')
set #i=0
While #j<>0
begin
declare #str varchar
set #str = SUBSTRING(#stringut,#i,#j-1-#i)
print #str+' '
set #i=#j
set #j=charindex(#stringut,'_')
end
PRINT #stringut -- print the name
set #contor=#contor-1
END
CLOSE cursorName -- close the cursor
DEALLOCATE cursorName -- Deallocate the cursor
That's becuase you have fetched value in cursor twice one before while loop and one within it. Write as:
DECLARE #contor int
DECLARE #stringut varchar(50)
DECLARE cursorName CURSOR -- Declare cursor
FOR
Select string FROM TestScene
Select #contor=count(*) from TestScene
OPEN cursorName -- open the cursor
print #contor
--FETCH NEXT FROM cursorName INTO #stringut-- no need
PRINT #stringut+' ' -- print the name
WHILE #contor>0
BEGIN
FETCH NEXT FROM cursorName INTO #stringut
declare #i int,#j int
set #j=charindex(#stringut,'_')
set #i=0
While #j<>0
begin
declare #str varchar
set #str = SUBSTRING(#stringut,#i,#j-1-#i)
print #str+' '
set #i=#j
set #j=charindex(#stringut,'_')
end
PRINT #stringut -- print the name
set #contor=#contor-1
END
CLOSE cursorName -- close the cursor
DEALLOCATE cursorName -- Deallocate the cursor
I have a question. I am working on cursors. Each time, after fetching the last records and printing its data’s, the cursor prints an addition line. To understand what I mean please consider the following sample example:
I want to print the information about only 10 customers.
USE Northwind
GO
DECLARE myCursor CURSOR
FOR SELECT TOP(10) ContactName FROM Customers
DECLARE #RowNo int,#ContactName nvarchar(30)
SET #RowNo=1
OPEN myCursor
FETCH NEXT FROM myCursor INTO #ContactName
PRINT LEFT(CAST(#rowNo as varchar) + ' ',6)+' '+ #ContactName
SET #RowNo=#RowNo+1
SET #ContactName=''
WHILE ##FETCH_STATUS=0
BEGIN
FETCH NEXT FROM myCursor INTO #ContactName
PRINT + LEFT(CAST(#rowNo as varchar) + ' ',6)+' '+ #ContactName
SET #RowNo=#RowNo+1
SET #ContactName=''
END
CLOSE myCursor
DEALLOCATE myCursor
Now look at the output:
1 Maria Anders
2 Ana Trujillo
3 Antonio Moreno
4 Thomas Hardy
5 Christina Berglund
6 Hanna Moos
7 Frédérique Citeaux
8 Martín Sommer
9 Laurence Lebihan
10 Elizabeth Lincoln
11
The row number 11 also has been printed. Is it a problem in a cursor or it always occurs?
Is there any way not to print this addition data? Thanks
(i use sql erver 2008)
Either...
FETCH NEXT FROM myCursor INTO #ContactName
WHILE ##FETCH_STATUS = 0
BEGIN
-- do stuff
FETCH NEXT FROM myCursor INTO #ContactName
END
Or...
WHILE ##FETCH_STATUS = 0
BEGIN
FETCH NEXT FROM myCursor INTO #ContactName
IF ##FETCH_STATUS = 0
BEGIN
-- do stuff
END
END
Or...
WHILE (1 = 1)
BEGIN
FETCH NEXT FROM myCursor INTO #ContactName
IF ##FETCH_STATUS <> 0
BREAK
-- do stuff
END
You mentioned you're using SQL Server 2008. With SQL Server 2005 or greater, you don't need a cursor at all to do what you want.
select top 10 left(cast(row_number() over(order by ContactName) as varchar)+ ' ', 6) + ContactName
from Customers
See how you have the printing logic duplicated? That's a pointer to what's going wrong. Your loop should look like this:
FETCH NEXT INTO #working_variables
WHILE ##FETCH_STATUS = 0
-- process #working_variables
FETCH NEXT INTO #working_variables
The only duplicated code should be the FETCH NEXT itself - the way you have it now, the last FETCH happens, but you PRINT a line before the WHILE can exit.
A FETCH at the end of the record set sets ##FETCH_STATUS to not 0.
The FETCH NEXT command should be the last line in the WHILE BLOCK.
USE Northwind
GO
DECLARE myCursor CURSOR
FOR SELECT TOP(10) ContactName FROM Customers
DECLARE #RowNo int,#ContactName nvarchar(30)
SET #RowNo=0
OPEN myCursor
FETCH NEXT FROM myCursor INTO #ContactName
WHILE ##FETCH_STATUS=0
BEGIN
SET #RowNo=#RowNo+1
SET #ContactName=''
PRINT + LEFT(CAST(#rowNo as varchar) + ' ',6)+' '+ #ContactName
FETCH NEXT FROM myCursor INTO #ContactName
END
CLOSE myCursor
DEALLOCATE myCursor
This is an off-by-one error. Here's a better way to iterate through a cursor, w/ less code duplication:
USE Northwind
GO
DECLARE myCursor CURSOR
FOR SELECT TOP(10) ContactName FROM Customers
DECLARE #RowNo int,#ContactName nvarchar(30)
SET #RowNo=0 -- initialize counters at zero, increment after the fetch/break
OPEN myCursor
WHILE 1=1 BEGIN -- start an infinite loop
FETCH NEXT FROM myCursor INTO #ContactName
IF ##FETCH_STATUS <> 0 BREAK
SET #RowNo=#RowNo+1
PRINT LEFT(CAST(#rowNo as varchar) + ' ',6)+' '+ #ContactName
END
CLOSE myCursor
DEALLOCATE myCursor
For extra points, use a cursor variable and declare w/ FAST_FORWARD and TYPE_WARNING, or STATIC for small datasets. eg:
DECLARE #cursor CURSOR
SET #cursor = CURSOR FAST_FORWARD TYPE_WARNING FOR
SELECT TOP (10) ContactName FROM Customers
OPEN #cursor
......
CLOSE #cursor
DEALLOCATE #cursor
CLOSE and DEALLOCATE are not strictly necessary, as the cursor variable will go out of scope at the end of the batch. It is still good form, however, as you might add more code at the end later on, and you should free up resources as early as possible.
TYPE_WARNING tells you when SQL Server implicitly converts the requested cursor type (FAST_FORWARD) to another type (typically STATIC), if the requested type is incompatible w/ your SELECT statement.