Please start any new threads on our new site at https://forums.sqlteam.com. We've got lots of great SQL Server experts to answer whatever question you can come up with.

 All Forums
 SQL Server 2000 Forums
 Transact-SQL (2000)
 Eliminate this cursor

Author  Topic 

SMerrill
Posting Yak Master

206 Posts

Posted - 2011-09-26 : 16:42:51
I enjoy reading this forum because of the "cursor bigots" that always say it can be done in a set-based method. I AGREE with you on that.
If you would like to have some more fun, here's another cursor that my customer is using.

The idea is to count continuous hit counts per item. The original table (@tmp) has one record for each hit. The output has the same number of records, but an additional CHC column.


SET NOCOUNT ON;
DECLARE @tmp TABLE (
ID INT NOT NULL PRIMARY KEY
, ITEM_ID INT NOT NULL
)

DECLARE @HIST TABLE (
ID INT NOT NULL PRIMARY KEY
, ITEM_ID INT NOT NULL
, CHC INT NOT NULL -- consecutive hit count
)
INSERT INTO @tmp
(ID, ITEM_ID )
VALUES
(1, 1)
, (2, 1)
, (3, 1)
--, (4, 1)
, (5, 1)
--, (6, 1)
, (7, 1)
, (8, 1)
--, (9, 1)
,(10, 1)

--,(11, 2)
,(12, 2)
,(13, 2)
--,(14, 2)
--,(15, 2)
--,(16, 2)
,(17, 2)
,(18, 2)
--,(19, 2)
,(20, 2)

DECLARE @ID AS INT
DECLARE @prev_ID AS INT

DECLARE POPULATE_HIST CURSOR FAST_FORWARD FOR

SELECT ID, ID - 1 AS previous_id
FROM @tmp
ORDER BY ID

OPEN POPULATE_HIST

FETCH NEXT FROM POPULATE_HIST INTO @ID , @PREV_ID

WHILE (@@FETCH_STATUS = 0)
BEGIN
INSERT INTO @HIST (ID, ITEM_ID, CHC)
SELECT curr.ID
, curr.ITEM_ID
, CASE WHEN prev.CHC IS NULL THEN 0
ELSE prev.CHC + 1 END
as CHC
FROM @tmp AS curr
LEFT OUTER JOIN
( SELECT
ID
, ITEM_ID
, CHC
FROM @HIST
WHERE ID = @PREV_ID

) AS prev
ON curr.ITEM_ID = prev.ITEM_ID
WHERE curr.ID = @ID
GROUP BY
curr.ID
, curr.ITEM_ID
, prev.CHC

FETCH NEXT FROM POPULATE_HIST INTO @ID ,@PREV_ID
END
CLOSE POPULATE_HIST
DEALLOCATE POPULATE_HIST

SELECT * FROM @HIST
/*
ID ITEM_ID CHC
1 1 0
2 1 1
3 1 2
5 1 0
7 1 0
8 1 1
10 1 0
12 2 0
13 2 1
17 2 0
18 2 1
20 2 0
*/
Enjoy,

~ Shaun Merrill
Seattle area

robvolk
Most Valuable Yak

15732 Posts

Posted - 2011-09-26 : 16:55:12
Well that was tough:
;WITH cte(id,item_id,seq) AS (
SELECT id, item_id, ROW_NUMBER() OVER (PARTITION BY item_id ORDER BY ID)-id seq FROM @tmp)
SELECT id, item_id, ROW_NUMBER() OVER (PARTITION BY item_id, seq ORDER BY id)-1 FROM cte ORDER BY id
I'm assuming you're using SQL 2008 even though this is posted in a SQL 2000 forum.
Go to Top of Page

SMerrill
Posting Yak Master

206 Posts

Posted - 2011-09-27 : 09:32:11
yes I am. Thanks, Rob!

I've never seen that usage. I will study it.

~ Shaun Merrill
Seattle area
Go to Top of Page

SMerrill
Posting Yak Master

206 Posts

Posted - 2011-09-27 : 15:48:01
Rob:
I have studied this and I love it. I still find it difficult to explain in English.
( By the way, just how does a person like you come up with this??)

My attempt:
The CTE.SEQ column is the difference between the partitioned row number, and the overall row number.
Knowing that we will partition by this difference again, here is why a difference was necessary:
1. d'uh . . .


~ Shaun Merrill
Seattle area
Go to Top of Page

robvolk
Most Valuable Yak

15732 Posts

Posted - 2011-09-27 : 16:22:48
The easiest way to see what the sequence does is to compare to a plain ROW_NUMBER():
SELECT id, item_id, ROW_NUMBER() OVER (PARTITION BY item_id ORDER BY ID) row_num,
ROW_NUMBER() OVER (PARTITION BY item_id ORDER BY ID)-id seq
FROM @tmp
When you have a continuous series of IDs, the difference between ID and row number will be constant (sequence). If there's a gap, a new sequence value will be generated since row number is always continuous. Since you're checking against different items, you have to PARTITION BY them.

So once the sequence value identifies gaps, you simply do the same row number calculation again, but PARTITION BY item and sequence. I used a CTE because I don't think you can nest a ROW_NUMBER() inside another, I haven't tried it though.

I didn't come up with this, I'm pretty sure I got it from Itzik Ben-Gan, or from someone who got it from him. He writes A LOT about using window functions to solve problems like this. He's written a number of the Inside SQL Server books for 2005 and 2008, and has examples in there, plus his monthly SQL Server Magazine column.

And if you think this is impressive, you ain't seen nothing yet. Read his books and articles.
Go to Top of Page
   

- Advertisement -