I am currently using a sql cursor to look up a table to update another table. I have a table which contains a lot of phrases. I want update another table to set 1 if any of those phrases falls into any of the column in the update table. I am using cursor and char to look for the phrase. The cursor is taking long time and I'm just wondering if I could use anything else instead of the cursor. Thanks. I'm using sql server and here's the code
declare #word varchar(max)
declare #aCursor cursor for
SELECT col from table
open acursor
fetch next from acursor into #word
while ##fetch_status=0
begin
SET #word = '' + #word + ''
UPDATE updatetable
SET updatecol = 'y'
FROM updatetable u, tableb b
WHERE u.id = b.id AND (CHARINDEX(#word, u.name) > 0 OR CHARINDEX(#word, u.city) >
fetch next from acursor into #word
end
close acursor
deallocate acursor
Take a look at: http://weblogs.sqlteam.com/jeffs/archive/2008/06/05/sql-server-cursor-removal.aspx that should get you way on your way. I, however, have come full cricle on the issue, for individual row manipulation, cursors ARE the way to go, performance is about the same as other methods and readability is 10x better than the other methods making maintaining the code a lot easier.
However, I don't have enough detail it seems to understand why you can't solve this with an update statement.
Related
I have to use a Stored Procedure - that I cannot change/modify. While it is a bit complicated, it can be simplified to be a SELECT statement i.e. with no RETURN or OUTPUT parameter. For the purpose of this discussion assume it to be something like:
SELECT [URL] as imgPath
FROM [mydatasource].[dbo].[DigitalContent]
I need to execute this Stored Procedure passing in the Row ID (SKU) of each row in a Table.
I use a cursor for this as below:
DECLARE #sku varchar(100)
DECLARE #imgPath varchar(500)
DECLARE c CURSOR FOR
SELECT [SKU]
FROM [mydatasource].[dbo].[PROD_TABLE]
OPEN c
FETCH NEXT FROM c INTO #sku
WHILE ##FETCH_STATUS = 0 BEGIN
EXEC #imgPath = [mydatasource].[dbo].[getImage] #sku
--UPDATE PROD_TABLE SET ImgPath=#imgPath WHERE SKU=#sku
SELECT #imgPath AS ImgPath
FETCH NEXT FROM c INTO #sku
END
CLOSE c
DEALLOCATE c
Unfortunately, the return value #imgPath comes back as 0 i.e. success. This results in 0s being inserted into my PROD_TABLE or dumped on the Console. However, as the getImage Stored Procedure executes, it dumps the correct values of imgPath to the console.
How do I get this correct value (i.e. the result of the SELECT in the Stored Procedure) in the Loop above, so that I can correctly update my PROD_TABLE?
Answer
Thanks to RBarryYoung suggestion, my final code looks like:
DECLARE #sku varchar(100)
DECLARE #imgPath varchar(500)
DECLARE c CURSOR FOR
SELECT [SKU]
FROM [mydatasource].[dbo].[PROD_TABLE]
OPEN c
FETCH NEXT FROM c INTO #sku
WHILE ##FETCH_STATUS = 0 BEGIN
CREATE TABLE #OutData ( imgPath varchar(500) )
INSERT INTO #OutData EXEC [mydatasource].[dbo].[getImage] #sku
--UPDATE PROD_TABLE SET ImgPath=(SELECT * FROM #OutData) WHERE SKU=#sku
SELECT * FROM #OutData
DROP TABLE #OutData
FETCH NEXT FROM c INTO #sku
END
CLOSE c
DEALLOCATE c
The performance may not be the best, but at least it works :-).
First, create a temp table (#OutData) whose definition matches the output dataset being returned by getImage.
Then, change your EXEC .. statement to this:
INSERT INTO #OutData EXEC [mydatasource].[dbo].[getImage] #sku
Response to the question: "Is it possible to insert the Key/Row ID into the Temp Table, that way I will not have to TRUNCATE it after each loop iteration?"
First, as a general rule you shouldn't use TRUNCATE on #temp tables as there are some obscure locking problems with that. If you need to do that, just DROP and CREATE them again (they're optimized for that anyway).
Secondly, you cannot modify the dataset returned by a stored procedure in any way. Of course once its in the #temp table you can do what you want with it. So you could add a KeyId column to #OutData. Then inside the loop make a second #temp table (#TmpData), and use INSERT..EXEC to dump into that table instead. Then INSERT..SELECT into #OutData by selecting from #TmpData, adding your KeyID column. Finally, DROP TABLE #TmpData as the last statement in your loop.
This should perform fairly well.
Sometimes executing code entirely inside SQL Server can be more difficult than doing so directly client-side, sending multiple queries calling the SProc (ideally batched in a single round-trip) and processing the results there directly.
Otherwise, the INSERT-EXEC method seems the easier if you absolutely can't modify the called procedure. There are a few alternative methods, all with some additional problems, shown here: http://www.sommarskog.se/share_data.html
Maybe there's a better way about this but this has to be somewhat dynamic.
From a vb.net form I need to restore or replace data from one table to another. The two tables are identical except for a couple different columns.
First I wrote some SQL to grab the column names of the table passed in. Then through ordinal position I get only the tables I want values from. I store these tables names in a temp table.
Now I want to get those values from the backup table using the temp table column names and place them in the master table.
So I guess I suppose I need a cursor to loop through in some way.. I haven't touched a cursor since college and wow.
I'll embarrass myself and post my current code.
SET #getColCURSOR = CURSOR FOR
SELECT name
FROM #MyTempTable --created previously as table only holding column names
OPEN #getColCURSOR
FETCH NEXT FROM #getColCURSOR
INTO #columnName
WHILE ##FETCH_STATUS = 0
BEGIN
select #columnName --this variable should as a column name and change
from AUDIT_TABLE a where a.ID = 7 -- 7 is just for testing is dynamic variable
FETCH NEXT FROM #getColCURSOR
INTO #columnName
END
CLOSE #getColCURSOR
DEALLOCATE #getColCURSOR
I'm not going to comment on whether this could be done without a cursor, since I'm a bit lost on what you're trying to do. But one issue with your cursor is that you can't parameterize a column name in a select statement. So you'll need to replace this:
WHILE ##FETCH_STATUS = 0
BEGIN
select #columnName --this variable should as a column name and change
from AUDIT_TABLE a where a.ID = 7 -- 7 is just for testing is dynamic variable
FETCH NEXT FROM getColCURSOR
INTO #columnName
END
--with dynamic SQL like this:
DECLARE #SQL nvarchar(max)
WHILE ##FETCH_STATUS = 0
BEGIN
set #SQL =
N'select ' + QUOTENAME(#columnName) + ' from AUDIT_TABLE a where a.ID = 7'
EXEC (#SQL); --w/o brackets assumes you've calling a stored proc
FETCH NEXT FROM getColCURSOR
INTO #columnName
END
That could possibly introduce other issues, since dynamic SQL statements execute in their own scope. I'd definitely encourage you to look into whether there's a set-based solution to this, since using dynamic SQL will make this even messier, and I don't think you'll be able to escape dynamic SQL if you want to use a variable for column names.
I want to reference the nth row of the #temptable (at the second SQL comment is below). What expression will allow me to do so?
DECLARE #counter INT
SET #counter = 0
WHILE (#counter<count(#temptable))
--#temptable has one column and 0 or more rows
BEGIN
DECLARE #variab INT
EXEC #variab = get_next_ticket 3906, 'n', 1
INSERT INTO Student_Course_List
SELECT #student_id,
-- nth result set row in #temptable, where n is #count+1
#variab
SET #counter = #counter +1
END
Cursor (will this work?):
for record in (select id from #temptable) loop
--For statements, use record.id
end loop;
Normally in a relational database like SQL Server, you prefer to do set operations. So it would be best to simply have INSERT INTO tbl SOMECOMPLEXQUERY even with very complex queries. This is far preferable to row processing. In a complex system, using a cursor should be relatively rare.
In your case, it would appear that the get_next_ticket procedure performs some significant logic which is not able to be done in a set-oriented fashion. If you cannot perform it's function in an alternative set-oriented way, then you would use a CURSOR.
You would declare a CURSOR on your set SELECT whatever FROM #temptable, OPEN it, FETCH from the cursor into variables for each column and then use them in the insert.
Instead of using a while loop (with a counter like you are doing) to iterate the table you should use a cursor
Syntax would be:
DECLARE #id int
DECLARE c cursor for select id from #temptable
begin
open c
fetch next from c into #id
WHILE (##FETCH_STATUS = 0)
BEGIN
--Do stuff here
fetch next from c into #id
END
close c
deallocate c
end
Here's what I'd like to do.
For each table in linkedserver.database whose tablename is like 'text%'
(inside loop)
A. If current_table exists locally, drop it
B. select * into table.name (local) from linkedserver.tablename (copy
schema + data)
C. Possibly check for errors and Print some text about it?
Next
Any idea if this script is possible? I'm very clueless about working with table names if it would be possible to
select * into #Variable_Storing_Table_Name
from LinkedServer.DB.#Variable_Storing_Table_Name
Well, here's how to do this using a cursor:
use database
go
declare #link_table nvarchar(255)
declare #local_table nvarchar(255)
declare table_list cursor for
select
tlink.name,
tlocal.name
from
linkedserver.database.sys.tables tlink
left outer join sys.tables tlocal on
tlink.name = tlocal.name
open table_list
fetch next from table_list into #link_table, #local_table
while ##FETCH_STATUS = 0
begin
begin try
if #local_table is not null
begin
sp_executesql N'drop table ' + quotename(#local_table)
end
sp_executesql N'select * into ' + quotename(#link_table) +
' from linkedserver.database..' + quotename(#link_table)
print #link_table + ' copied.'
end try
begin catch
print 'Error: ' + ERROR_MESSAGE()
end catch
fetch next from table_list into #link_table, #local_table
end
close table_list
deallocate table_list
While cursors should generally be avoided, here you're looking to do a lot of logic behind each and every row. So, here it is. What it does is grab all of the linked tables and match any of the local tables to those, or null if the local table doesn't exist. This places it in a cursor, which we can use to iterate through the rowset.
The fetch next command grabs the next row from our cursor and then applies your logic to it (drop it if the local table exists, then do a select * into...).
You can catch errors one of two ways. I used the try...catch block, but you can also check ##ERROR and see if it's not equal to zero. Really, whatever you feel most comfortable with.
As a disclaimer for the anti-cursor crowd: Cursors aren't evil, they're just often used improperly.
There is an undocumented SQL Server function called sp_foreachtable that might do what you want. I'm not sure if it works on linked databases though... a Web search might turn something up.
I have the following TSQL codes:
-- 1. define a cursor
DECLARE c_Temp CURSOR FOR
SELECT name FROM employees;
DECLARE #name varchar(100);
-- 2. open it
OPEN c_Temp;
-- 3. first fetch
FETCH NEXT FROM c_Temp INTO #name;
WHILE ##FETCH_STATUS = 0
BEGIN
print #name;
FETCH NEXT FROM c_Temp INTO #name; -- fetch again in a loop
END
-- 4. close it
....
I use the name value only in a loop block. Here I have to
define a cursor variable,
open it,
fetch twice and
close it.
In PL/SQL, the loop can be like this:
FOR rRec IN (SELECT name FROM employees) LOOP
DBMS_OUTPUT.put_line(rRec.name);
END LOOP;
It is much simpler than my TSQL codes. No need to define a cursor. It is created dynamically which is accessible within a loop block (much like C# for loop). Not sure if there something similar like this in TSQL?
Something along these lines might work for you, although it depends on having an ID column or some other unique identifier
Declare #au_id Varchar(20)
Select #au_id = Min(au_id) from authors
While #au_id IS NOT NULL
Begin
Select au_id, au_lname, au_fname from authors Where au_id = #au_id
Select #au_id = min(au_id) from authors where au_id > #au_id
End
Cursors are evil in Sql Server as they can really degrade performance - my favoured approach is to use a Table Variable (>= Sql Server 2005) with an auto inc ID column:
Declare #LoopTable as table (
ID int identity(1,1),
column1 varchar(10),
column2 datetime
)
insert into #LoopTable (column1, column2)
select name, startdate from employees
declare #count int
declare #max int
select #max = max(ID) from #LoopTable
select #count = 1
while #count <= #max
begin
--do something here using row number '#count' from #looptable
set #count = #count + 1
end
It looks pretty long winded however works in any situation and should be far more lightweight than a cursor
Since you are coming from an Oracle background where cursors are used frequently, you may not be aware that in SQl Server cursors are performance killers. Depending on what you are actually doing (surely not just printing the variable), there may be a much faster set-based solution.
In some cases, its also possible to use trick like this one:
DECLARE #name VARCHAR(MAX)
SELECT #name = ISNULL(#name + CHAR(13) + CHAR(10), '') + name
FROM employees
PRINT #name
For a list of employee names.
It can also be used to make comma-separated string, just replace + CHAR(13) + CHAR(10) with + ', '
Why not simply just return the recordset using a select statement. I assume the object is to copy and paste the values in the UI (based on the fact that you are simply printing the output)? In Management studio you can copy and paste from the grid, or press +T and then run the query and return the results as part of the messages tab in plain text.
If you were to run this via an application, the application wouldn't be able to access the printed statements as they are not being returned within a recordset.