April 12, 2006 at 8:35 am
Yes, yes, I know...avoid cursors...and, I do. In fact, that is why I now have a question. I am reviewing a third party tool that does incremental data retrieval and I'm assuming it must use cursors to do the page refreshes.
I assume this because lazy where clause criteria can lead to hundreds of thousands of records being returned, yet this application's forms quickly return the first ten rows. If you hit page down, it very quickly returns another 10 rows and so on and so forth.
So, as an experiment I created a cursor against a table with 2,000,000 million rows and open it. Wow - it did that in milliseconds. Next, I use a while loop to fetch 10 records at a time. Each 10 records again takes milliseconds.
Ok, so now I see how the vendor is probably doing it. You may think this is a funny think to have to experiment with, but I always work with sets of data - I've never considered retrieving 10 records at a time.
Now I'm curious about the physical implementation of the cursor. What the heck would the execution plan look like? It obviously doesn't put all 2 million records into a temp object when I declare the cursor or it would take longer than a couple of milliseconds. I can't imagine that the cursor sits around with a lock on all joined tables and keeps track of some ordinal position or something... What's the deal?
April 12, 2006 at 8:57 am
I think it pulls some rows and then starts to return them. As you access more, it pulls more. Curious, is there an ORDER BY clause in the select?
The other thing to be sure is that it's pulling a read only, firehose cursor, to be sure that you won't block anything else.
April 12, 2006 at 4:48 pm
It could still be done in set based logic. If the calling app sends in the page number it wants and the page size, the following query logic can be used.
-- Count the total rows selected
SELECT @RowCount = COUNT(*)
FROM #Page
-- Calculate the max number of pages that can be displayed
SELECT @MaxPages = CEILING((@RowCount/CONVERT(decimal(10,2),@PageSize)))
-- Reset passed Page to the highest available page if it is greater than the
-- Max pages.
SELECT @Page = CASE WHEN @MaxPages < @Page
THEN @MaxPages
ELSE @Page
END
-- Return Total Rows result set
SELECT TotalRows = @RowCount
-- Return Case List result set
SELECT <whatever>
FROM #Page [CASE]
WHERE RowID >= (@Page * @PageSize) - @PageSize + 1
AND RowID <= (@Page * @PageSize)
ORDER BY RowID
Cheers
Mark
April 12, 2006 at 6:29 pm
Mark, yes, I know this can be solved using a set. Although, based on the characterstics of the forms and the speed of the initial load, etc., the only way I have been able to create similar functionality/performance was with a cursor. ...which is why I was so curious about the nature of the physical implimentation of the cursor.
I built a few different scenerios using a table with 2 million rows. The only way I was able to get an instantaneous 10 rows ,and 10 rows only, (upon a user driven event) was using the cursor.
April 12, 2006 at 11:23 pm
How you define where to start cursoring if you switch from 91-100 range to 101-110 range?
Do you do cursoring through first 100 rows?
_____________
Code for TallyGenerator
April 13, 2006 at 4:34 am
About the physical implementations of cursors.. Well it apparently depends.. However, I do think that one shouldn't be too surprised if there would be more locks taken and more tempspace used under the hood, than what one would be led to believe.
Here's a brief overview snippet from BOL:
ODBC, ADO, and DB-Library define four cursor types supported by Microsoft® SQL Server™2000. The DECLARE CURSOR statement has been extended; thus you can specify the four cursor types for Transact-SQL cursors. These cursors vary in their ability to detect changes to the result set and in the resources, such as memory and space in tempdb, they consume. A cursor can detect changes to rows only when it attempts to fetch those rows a second time. There is no way for the data source to notify the cursor of changes to the currently fetched rows. The ability of a cursor to detect changes is also influenced by the transaction isolation level.
The four API server cursor types supported by SQL Server are:
Static cursors detect few or no changes but consume relatively few resources while scrolling, although they store the entire cursor in tempdb. Dynamic cursors detect all changes but consume more resources while scrolling, although they make the lightest use of tempdb. Keyset-driven cursors lie in between, detecting most changes but at less expense than dynamic cursors.
Although the database API cursor models consider a forward-only cursor to be a distinct type of cursor, SQL Server does not. SQL Server considers both forward-only and scroll as options that can be applied to static, keyset-driven, and dynamic cursors.
-- end snip ---
Cursors is a tricky subject, to say the least.
/Kenneth
Viewing 6 posts - 1 through 5 (of 5 total)
You must be logged in to reply to this topic. Login to reply