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.
Related
I have a stored procedure like below which works fine.
declare db_cursor cursor for
select Atmosphere, Region, PreATR
from myTbl
open db_cursor
fetch next from db_cursor into #Atmosphere, #Region, #PreATR
while ##FETCH_STATUS = 0
begin
if #PreATR = 1
set #q = 'insert into tblA ... '
else
set #q = 'insert into tblB ...
end
exec(#q)
fetch next from db_cursor into #Atmosphere, #Region, #PreATR
end
close db_cursor
deallocate db_cursor
However now I need to adjust it. So I want to add another if else statement like below. When I do this though the line below becomes highlighted
close db_cursor
Incorrect syntax near 'close'. expecting CONVERSATION
open db_cursor
fetch next from db_cursor into #Atmosphere, #Region, #PreATR
while ##FETCH_STATUS = 0
begin
if #Region = 55
set #someVar = 1
else
set #someVar = 1
end
if #PreATR = 1
set #q = 'insert into tblA ... '
else
set #q = 'insert into tblB ...
end
exec(#q)
fetch next from db_cursor into #Atmosphere, #Region, #PreATR
end
close db_cursor
deallocate db_cursor
why does adding this extra if else statement cause this behavior?
You are getting the error because if else syntax was incorrect and also single quote was missing in the second insert statement, try below-updated query, I have removed end after else statement and added a single quote in the second insert statement -
open db_cursor
fetch next from db_cursor into #Atmosphere, #Region, #PreATR
while ##FETCH_STATUS = 0
begin
if #Region = 55
set #someVar = 1
else
set #someVar = 1
if #PreATR = 1
set #q = 'insert into tblA ... '
else
set #q = 'insert into tblB ...'
exec(#q)
fetch next from db_cursor into #Atmosphere, #Region, #PreATR
end
close db_cursor
deallocate db_cursor
Note: Whenever there are multiple statements with if and else block then you must use begin and end like below -
IF #Var = 1
BEGIN
PRINT '1';
END
ELSE
BEGIN
PRINT 'not 1';
END
Im sure you could do this much simpler and faster without a cursor, unfortunate there is not enough details in the question to write an example that can get you started
probably you need something like this
insert into tblA (your fields here)
select t.Atmosphere, t.Region, t.PreATR
from myTbl t
where t.PreATR = 1
and more conditions here...
insert into tblB (your fields here)
select t.Atmosphere, t.Region, t.PreATR
from myTbl t
where t.PreATR <> 1
and more conditions here...
Is it possible to update multiple tables from a query result?
I've tried using a cursor. But it's still not working.
Here's the code :
DECLARE #TableName VARCHAR(MAX)
DECLARE db_cursor CURSOR FOR
SELECT TABLE_NAME
FROM information_schema.columns
WHERE column_name = 'Code1';
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #TableName
WHILE ##FETCH_STATUS = 0
BEGIN
UPDATE #TableName
SET Code1 = Code + '_' + Type
FETCH NEXT FROM db_cursor INTO #TableName
END
CLOSE db_cursor
DEALLOCATE db_cursor
Hypothesis
I suppose that OP is trying to dynamically build and execute SQL-code for all tables that have column Code1
Solution
Solution (one of many) could be:
Build cursor of created SQL-expressions
In cycle exec created expressions
Example code
DECLARE #sql_code varchar(max)
DECLARE code_cursor CURSOR FOR
SELECT DISTINCT
'UPDATE '+ TABLE_NAME + ' SET Code1= Code + ''_'' + Type;' AS SQL_CODE
FROM
information_schema.columns -- WHERE column_name = 'Code1';
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #sql_code
WHILE ##FETCH_STATUS = 0
BEGIN
exec(#sql_code)
FETCH NEXT FROM db_cursor INTO #TableName
END
CLOSE db_cursor
DEALLOCATE db_cursor
Caution
I did not tested it (of cause - I don't have similar DB) - so be careful.
Update
It's even simpler would be to modify OP code like this:
DECLARE #TableName VARCHAR(MAX)
DECLARE db_cursor CURSOR
FOR SELECT DISTINCT TABLE_NAME -- note DISTINCT here
FROM information_schema.columns WHERE column_name = 'Code1';
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #TableName
WHILE ##FETCH_STATUS = 0
BEGIN
EXEC('UPDATE '+ #TableName + ' SET Code1 = Code + ''_'' + Type') -- note EXEC here
FETCH NEXT FROM db_cursor INTO #TableName
END
CLOSE db_cursor
DEALLOCATE db_cursor
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
I am writing a Scalar function for my web application. I want to calculate the date difference between an employee's Join Date and Resign Date.
I have got most of my code working, but I just cannot figure out how to use parameter variables in cursor.
Let say I have this block of code
Declare myCursor Cursor for
Declare #join_date datetime
Declare #resign_date datetime
Declare #emp_stat nvarchar(50)
--What I have been trying to do. (Not working)
Select #join_date = Convert(datetime, join_date), #emp_stat = Convert(datetime, emp_stat) from t_emp_info where .....
OPEN product_cursor
WHILE ##FETCH_STATUS = 0
BEGIN
if (#emp_stat = 'P')
Begin
//DateDiff .....
End
FETCH NEXT FROM vendor_cursor
INTO #join_date , #emp_stat
End
Close myCursor
DEALLOCATE myCursor;
I can't get this working, but what I want is I want to store the values in the parameter so I can use it in the if condition statement. Not sure how to fix this. Help will be appreciated
Your cursor names are mixed up and your assignment is in the wrong place. Try:
Declare myCursor Cursor for
Select join_date, emp_stat, resign_date from t_emp_info where .....
Declare #join_date datetime
Declare #resign_date datetime
Declare #emp_stat nvarchar(50)
OPEN myCursor
FETCH NEXT FROM myCursor
INTO #join_date , #emp_stat , #resign_date
WHILE ##FETCH_STATUS = 0
BEGIN
//Your logic here
FETCH NEXT FROM myCursor
INTO #join_date , #emp_stat, #resign_date
End
Close myCursor
DEALLOCATE myCursor;
However you should avoid the cursor. What is wrong with:
SELECT DATEDIFF(day, MAX(join_date), case when emp_stat = 'P' then getdate() else MAX(resign_date) end)
from t_emp_info
GROUP BY employee_id
Or similar?
We have triggers on a table called OSPP to save specific data to a table for later use.
I get the following error in SAP when adding more than one line at a time to the table.
Invalid Cursor State
We have SQL Server 2005 SP3 (but I tried it on a clean 2005 install, on SP1 and SP2)
The one trigger :
CREATE TRIGGER [dbo].[tr_OSPP_Insert]
ON [dbo].[OSPP]
FOR INSERT
AS
BEGIN
Declare #ItemCode varchar(255)
Declare #CardCode varchar(255)
Declare #Price decimal(18,2)
Declare #ListNum bigint
Declare #ID bigint
Declare #Remote char(1)
DECLARE db_cursor CURSOR FOR
SELECT ItemCode, CardCode, Price, ListNum
FROM INSERTED
OPEN db_cursor
FETCH NEXT
FROM db_cursor INTO #ItemCode, #CardCode, #Price, #ListNum
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #Remote = isnull(U_Remote, 'N') FROM OITM WHERE ItemCode = #ItemCode
IF ltrim(rtrim(upper(#Remote))) = 'Y'
BEGIN
SELECT #ID = U_ID FROM [dbo].[#BDS_MAINTENANCE]
UPDATE [dbo].[#BDS_MAINTENANCE] set U_ID = U_ID + 1
INSERT INTO [dbo].[#BDS_REMOTESPECIALPRICELIST]
(
Code,
[Name],
U_ID,
U_ItemCode,
U_CardCode,
U_Price,
U_ListNum,
U_TransactionType,
U_Uploaded
) VALUES (
#ID,
'_' + cast(#ID as VARCHAR(50)),
#ID,
#ItemCode,
#CardCode,
#Price,
#ListNum,
1,
0
)
FETCH NEXT
FROM db_cursor INTO #ItemCode, #CardCode, #Price, #ListNum
END
CLOSE db_cursor
DEALLOCATE db_cursor
END
END
We also tried :
CREATE TRIGGER [dbo].[tr_OSPP_Insert]
ON [dbo].[OSPP]
FOR INSERT
AS
BEGIN
SELECT * INTO [#TEMPTABLE222] FROM INSERTED
END
But still get the same error.
Do you guys have any idea what is wrong?
Thanks in advance!
I count three Begins, and three Ends. But it's the second pair that represent the cursor loop - so I'd move your Close/Deallocate to be after the second End, rather than before. E.g:
FETCH NEXT
FROM db_cursor INTO #ItemCode, #CardCode, #Price, #ListNum
END
CLOSE db_cursor
DEALLOCATE db_cursor
END
Probably needs to be:
END
FETCH NEXT
FROM db_cursor INTO #ItemCode, #CardCode, #Price, #ListNum
END
CLOSE db_cursor
DEALLOCATE db_cursor
(I've also moved the fetch next one level out, since otherwise you only move the cursor forwards inside your IF condition)
And one style comment (can't resist). It's generally considered good practice to SET NOCOUNT ON within the body of a trigger, to avoid sending lots of extra n rows affected messages.