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.
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 'Knowledge' table where I'm adding tags. I'm saving a many-to-many relationship to other tables. Let me show my table structure:
I want to store a comma delimited string in a single table of persons. For example:
Id
TagNames
28
Azure, SQL, WebConfig
29
Network
Thanks a lot of for answers.
I found that. I used cursor and I'm listing my tags in one column.
SELECT kn.ID,Title,ExplainProblem,SolutionDescription,kn.CreateUserID, M.Name +'
' + M.Surname as 'TechNameAndSurname',kn.CreateDate,IsPrivate,kn.IsActive,
case
when kn.IsPrivate=1 then 'Sadece Ben'
when kn.IsPrivate=0 then 'Seçili Gruplar'
else ''
end WhoIsShowing
into #temp FROM Knowledges kn
inner join Members M on M.ID = kn.CreateUserID where kn.IsActive=1
alter table #temp add TagsNames nvarchar(250)
DECLARE #ID int
DECLARE db_cursor CURSOR FOR SELECT Id FROM #temp
OPEN db_cursor FETCH NEXT FROM db_cursor INTO #ID
WHILE ##FETCH_STATUS = 0
BEGIN
IF OBJECT_ID('tempdb..#tags') IS NOT NULL DROP TABLE #tags
select tag.TagName,ktg.ID into #tags from KnowledgeTags ktg
inner join Tags tag on tag.ID = ktg.TagID
where ktg.KnowledgeID=#ID
order by ktg.TagID
--
DECLARE #tags nvarchar(250) = ''
DECLARE #kID int, #tagName nvarchar(250)
DECLARE db_cursor2 CURSOR FOR SELECT ID,TagName FROM #tags
OPEN db_cursor2 FETCH NEXT FROM db_cursor2 INTO #kID, #tagName
WHILE ##FETCH_STATUS = 0
BEGIN
SET #tags = #tags + ', ' + #tagName
FETCH NEXT FROM db_cursor2 INTO #kID, #tagName
END CLOSE db_cursor2 DEALLOCATE db_cursor2
--
update #temp set TagsNames = RIGHT(#tags, LEN(#tags)-1) where ID = #ID
FETCH NEXT FROM db_cursor INTO #ID
END CLOSE db_cursor DEALLOCATE db_cursor
SELECT * FROM #temp
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...
I'm trying to iterate over some tables and clear all records.
My code is the following :
DECLARE #table varchar(100)
DECLARE db_cursor CURSOR FOR select name from sys.tables where name like '%cfe_%'
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #table
WHILE ##FETCH_STATUS = 0
BEGIN
print #table
delete from #table
FETCH NEXT FROM db_cursor INTO #table
END
CLOSE db_cursor
DEALLOCATE db_cursor
But I receive "Must declare the table variable "#table" at the line "delete..."
I can't see the error.
Thank you
You shoud use dynamic query,
DECLARE #table varchar(100)
,#v_str nvarchar(200)
DECLARE db_cursor CURSOR FOR select name from sys.tables where name like '%cfe_%'
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #table
WHILE ##FETCH_STATUS = 0
BEGIN
print #table
set #v_str = 'delete from '+#table
exec(#v_str)
FETCH NEXT FROM db_cursor INTO #table
END
CLOSE db_cursor
DEALLOCATE db_cursor
You need dynamic delete statement... Try this :
DECLARE #cmd VARCHAR(4000)
DECLARE #table varchar(100)
DECLARE db_cursor CURSOR FOR select name from sys.tables where name like '%cfe_%'
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #table
WHILE ##FETCH_STATUS = 0
BEGIN
SET #cmd = 'DELETE FROM '+#table
EXEC (#cmd)
FETCH NEXT FROM db_cursor INTO #table
END
CLOSE db_cursor
DEALLOCATE db_cursor
Even better would be to not use a cursor here. Looping in sql is a last resort. Also, your query is not going to do exactly what you think it will because you are using like and wanting to find an underscore. The underscore in a LIKE predicate requires it to be escaped with square brackets. As posted your query will return any table with cfe in the name not cfe_.
Once you are comfortable that the dynamic sql string is what you want you can uncomment it to execute it.
declare #SQL nvarchar(max) = ''
select #SQL = #SQL + 'delete from ' + name + ';'
from sys.tables
where name like '%cfe[_]%'
select #SQL
--exec sp_executesql #SQL
We can also use while loop for this process
DECLARE #Min Int,#max Int
IF Object_id('Tempdb..#DeleteList')IS NOT NULL
DROP TABLE #DeleteList
CREATE TABLE #DeleteList (Id Int Identity,Name varchar(100))
INSERT INTO #DeleteList(Name)
SELECT name FROM sys.tables WHERE CHARINDEX('cfe_',name)>0
SELECT #Min=Min(Id) FROm #DeleteList
SELECT #max=MAX(Id) FROm #DeleteList
While(#Min<=#max)
Begin
Declare #TableName Varchar(50),
#Sql Nvarchar(max)
SELECT #TableName=Name From #DeleteList Where id=#Min
SET #Sql='DELETE From '+#TableName
Exec (#Sql)
SET #Min=#Min+1
END
But if the deleting tables have foreign key refrences it will throw error so that first you need delete records from child and then go to Parent table
You will need to do something like;
EXEC sp_executesql #statement = N'DELETE FROM ' + #table
because currently you are trying to delete from a String variable, not the table named the same as the variable
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.