August 15, 2005 at 9:23 pm
OK, people keep telling me cursors are evil and there is always a way to do something without using them. I keep running into situations where I can't think of any other way to do something about it. Here's one that I had nothing to do with: http://www.sqlservercentral.com/scripts/contributions/1557.asp
So, show me how to do what this does without using cursors. (Using SQL Server 2000 technology. With the new datatypes coming out in 2005, I could figure out how to do this without cursors.)
Don't tell me it can be done, do it. There's an extremely simple example of when I can't figure out how to do something without cursors. Just re-write his code without cursors.
August 15, 2005 at 9:37 pm
Oh, yea, don't use the underlying table dbo.sysproperties, that's an extremely simple way to build your dictionary without cursors that I've used a long time ago. Use the function that obsfucates the process like the author of the above code did.
August 15, 2005 at 9:49 pm
This sort of what you're looking for?
Create table #tblDataDictionary ( table_id [sql_variant] , table_name [sql_variant] , column_order [sql_variant] , column_name [sql_variant] , column_description [sql_variant] )
INSERT INTO #tblDataDictionary SELECT o.[id] as 'table_id' , o.[name] as 'table_name' , c.colorder as 'column_order' , c.[name] as 'column_name' , e.value as 'column_description' FROM sysobjects o INNER join syscolumns c ON o.id = c.id LEFT join ::FN_LISTEXTENDEDPROPERTY(N'MS_Description', N'user',N'dbo',N'table', null, N'column', null) e on c.name = e.objname WHERE o.type = 'U' AND o.status > 1 ORDER BY c.colorder
SELECT * FROM #tblDataDictionary
DROP TABLE #tblDataDictionary
--------------------
Colt 45 - the original point and click interface
August 15, 2005 at 10:39 pm
Try again
select * from ::FN_LISTEXTENDEDPROPERTY(N'MS_Description', N'user',N'dbo',N'table', null, N'column', null) e
produces null results, not surprisingly, so does:
SELECT o.[id] as 'table_id' , o.[name] as 'table_name' , c.colorder as 'column_order' , c.[name] as 'column_name' , e.value as 'column_description'
FROM sysobjects o INNER join syscolumns c ON o.id = c.id LEFT join ::FN_LISTEXTENDEDPROPERTY(N'MS_Description', N'user',N'dbo',N'table', null, N'column', null) e on c.name = e.objname
WHERE o.type = 'U' AND o.status > 1 AND e.value is not null
ORDER BY c.colorder
This in a DB where
select * from ::FN_LISTEXTENDEDPROPERTY(N'MS_Description', N'user',N'dbo',N'table', N'CopyTable', N'column', null) e
does produce results.
Even if it did work, multiple tables use the same variable name. At many locations with very different meanings. If you had 10 variable names that matched and a definition for each, your join would produce 100 results. (If it worked.)
Like I said, creating the dictionary using dbo.sysproperties without cursors is simple. I want you to do this with the function the author used and no cursors.
Create table #tblDataDictionary
(table_id int,
table_name sysname,
column_order tinyint,
column_name nvarchar(256),
column_description [sql_variant])
--Add table and column definitions
insert into #tblDataDictionary
SELECT o.id, o.name, c.colid, c.name, p.value
FROM dbo.sysobjects o
left join dbo.sysproperties p on o.id=p.id and p.name='MS_Description'
full outer join dbo.syscolumns c on o.id=c.id and p.smallid=c.colid
where o.type = 'U' and o.status > 1
--Add columns that aren't defined in the dictionary
insert into #tblDataDictionary
SELECT o.id, o.name, c.colid, c.name, null
FROM dbo.sysobjects o
join dbo.syscolumns c on o.id=c.id
left join #tblDataDictionary p on o.id=p.table_id and p.column_name=c.name
where o.type = 'U' and o.status > 1 and p.column_name is null
-- Lists table definitions that have been defined, something not in the author's example
select * from #tblDataDictionary
where column_description is not null
and column_name is null
order by table_name
-- Lists column definitions that have been defined
select * from #tblDataDictionary
where column_description is not null
and column_name is not null
order by table_name, column_name
-- Lists table definitions that have not been defined
select * from #tblDataDictionary
where column_description is null
and column_name is null
order by table_name
-- Lists column definitions that have not been defined
select * from #tblDataDictionary
where column_description is null
and column_name is not null
order by table_name, column_name
August 15, 2005 at 10:59 pm
Sorry, I can't reproduce your results, I don't have any databases where the MS_Description property is used.
Honestly, I can't recall using it in any of the databases I've worked on over the years. Generally the data dictionary was seperate from the database in tools like ERWin and ER/Studio.
Are you trying to say that because you can't find a non-cursor solution to this problem then cursors are not evil
--------------------
Colt 45 - the original point and click interface
August 16, 2005 at 1:58 pm
The properties support is built into the Enterprise Manager toolset. The definition library travels with the database. It doesn't depend on 3rd party software your customer may not have to see the definitions. SQL Server provides built-in procs that allow you to expand the properties in other directions than using name='MS_Description'. All good reasons to use their services.
The interfaces to access the definition library suck. If they didn't, 3rd party software wouldn't have a chance in H of succeeding. The article written about it wouldn't have been written. The definition library is not independent of the DB. (See, the same thing going for it, goes against it as well.)
I'm kind of curious, do you know if any of your 3rd party software is using dbo.sysproperties to store it's information?
I don't really care if you like or dislike the toolset, what I'm asking about is how to get results where I don't see any other way than using a cursor.
Show me how to run "DBCC INPUTBUFFER (spid)" for every spid that's currently active without using a cursor.
August 16, 2005 at 2:00 pm
I have seen misuse of cursors, I know they are a performance killer and they are the lazy way for a beginner to find a solution. In that respect, they are a little bit evil. I think that if they are being used in a heavily used production code, that code should be seriously reviewed to see if there isn't a set solution to do the same thing. I keep running into situations where a cursor seems like the best choice, and yes, I believe it isn't evil. It's no more evil than a gun is. It all depends on who is handling it.
In this particular example, there is a solution that does not need cursors in the SQL Server environment, in fact I provided a solution. Since I don't plan on leaving the SQL Server environment, it is a very satisfying solution. If I was trying to build a general purpose solution to this particular problem, this isn't a satisfying solution. (First, I'd have to know if extended properties are an ANSI standard and if the functions/views I was using come from ANSI. I'd have to care enough about it, to find out. I certainly wouldn't hardcode my solution to depend on the key 'MS_Description'.)
What is interesting about this function is that it provides a result set that is dependent on the environment. You can in-line call a function that produces a scalar result in a select statement but you can't do that with a function that produces a result set. I don't see how you could do it without cursors.
Another place where I end up using cursors, I know is because of my lack of knowledge. This is when I am building scripts where the final result will be longer than 8K bytes. I've read about ntext editing and I don't get it. Could someone show an example of creating an ntext field in a temp table, getting a pointer to a row on that table and adding 'exec ::FN_LISTEXTENDEDPROPERTY(N''MS_Description'',N''user'',N''dbo'',N''table'',N''' + name + ''', N''column'', null)' + char(13) + char(10) from dbo.sysobjects? (Yes, I know this can be put on individual rows in a result set, but how would code then execute it without using a cursor? What about situations where one command would exceed 8K bytes?)
The third place where I find cursors are useful is in scripts where I am not concerned in performance and using them breaks up the work into slightly more readable chunks of code. This is one argument I know I'll never win with cursor-hater purists. Let's just agree to disagree.
Oh, I figured out why my first join didn't include non-defined columns. This single insert would work instead of the two I gave in my non-cursor example:
--Add table and column definitions
insert into #tblDataDictionary
SELECT o.id, o.name, c.colid, c.name, p.value
FROM dbo.sysobjects o
left join dbo.sysproperties p on o.id=p.id and p.name='MS_Description'
join dbo.syscolumns c on o.id=c.id or (p.id=c.id and p.smallid=c.colid)
where o.type = 'U' and o.status > 1
August 16, 2005 at 2:42 pm
If it's just looping over all the tables, you can do that with a while loop (but since it's still completely procedural, I classify this as cheating :-):
1. Create a table variable with an identity column and a column for table names
2. Stuff it full of the list of tables
3. Declare i = 1
4. While i <= max of table list
begin
do things for the ith table
i = 1 + 1
end
Does that accomplish the task?
August 16, 2005 at 3:57 pm
Using an even more inefficient method than cursors, sure it does!
I'm looking more for a "set" answer from the "set purists" who don't like any cursors because there are more efficient ways of doing things using sets. That cursors are used by people who don't understand how to operate in sets. I certainly agree with that point, your solution is no better in that regard.
Show me how to run "DBCC INPUTBUFFER (spid)" for every spid that's currently active with a set answer, not using cursors or individual looping logic. Since this command is going to give 1 result row, show me how to get a scaler answer from just one of the fields of this result set in a select statement that returns every spid.
August 16, 2005 at 4:22 pm
I am probably considered on of those "set purists". My answer to your challenges is simple; do no use SQL Server as a business logic server, especially not for procedural code. I usually say that "a problem that can not be handled using set based T-SQL is probably not a problem for the DBMS to solve". If I needed to run DBCC INPUTBUFFER for each spid I could easily code that functionality in a C# application.
It is not hard to come up with situations that can not be handled without cursors and/or dynamic sql in SQL Server. Here is one: "For every table in every database, check every column and see if it is a varchar column. If it is, take every second character of that column and insert into a new table which should be named after the table and column where you are currently searching." But does that mean we should implement this in SQL Server?
I am not blind and would for instance never argue that cursors should be removed from SQL Server. However, for handling problems normally handled by RDBMSs I avoid them because I think there is a set based solution for every possibility that will always outperform the cursor.
August 16, 2005 at 4:58 pm
Chris, that's one of the best answers I've seen regarding this quesion. Using a string to store generated script output certainly solves my ntext question. Now all I have to do is figure out how to organize all these little customized C# programs that I'll have to write that don't belong in the business model and can be easily done using SQL text files instead and I won't have to write cursor logic again.
You are absolutely right, for production work, highly re-used business logic, if SQL can't do it without using cursors, it probably doesn't belong in SQL. I'm not convinced for low use/low performance impact code that using cursors is wrong.
August 16, 2005 at 6:23 pm
Agreed!
But, though my While/Looping answer was tongue-in-cheek, here's a more serious question: what about locking? Does a Declare Cursor on (stuff) lock more (stuff) than would a While loop that processes items one at a time? Or is the difference negligible?
August 16, 2005 at 9:11 pm
Hmmm, hadn't thought of that angle. I don't know enough about locking to give a definitive answer. What isolation level are you processing at in your script? What isolation level are interacting scripts going to run at? Are all of your script commands inside of a transaction? What type of cursor? If you want to argue that scrollable updateable cursors are evil, I don't want to fight that battle because I don't feel like being General Custer. I was thinking about a fast_forward read committed cursor outside of transaction level processing. I believe that produces a snapshot result set at the open command that is read locked on every record for the duration of the open command. If it's inside of a transaction, I'd guess it's read locked for the duration of the transaction.
Your loop processing would read lock the same information for the duration of creating the temporary table. The act of writing the result set into a table would cause a little more overhead, so I'd guess you might lock the information a split second longer outside of a transaction. A transaction would lock your resources longer because your loop should take longer.
Then the impact of what we are doing inside of the loop comes into play, outside of a transaction I'd say a push, inside I'd say a cursor should have less impact because the loop should take less time and the impacts would come in the same order with either method.
I'd guess hands down a loop would beat a scrollable cursor because the whole result set is read locked from open to close of the cursor.
August 16, 2005 at 9:40 pm
You also don't have the overhead of maintaining the cursor data in memory.
--------------------
Colt 45 - the original point and click interface
August 17, 2005 at 11:29 am
You also don't have the overhead of maintaining the cursor data in memory.
Well, if you have a temp table that starts with @, you still have the memory overhead. If you have a temp table that starts with # or ##, you are increasing contention for tempdb resources as you make your individual inquiries.
Oh, wait, I'm building the temp table as I go along, so I'm building either more memory storage or more tempdb resource contention problems too.
I don't know enough about the mechanics to argue this either way.
Viewing 15 posts - 1 through 15 (of 17 total)
You must be logged in to reply to this topic. Login to reply