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_HISTSELECT * FROM @HIST /*ID ITEM_ID CHC1 1 02 1 13 1 25 1 07 1 08 1 110 1 012 2 013 2 117 2 018 2 120 2 0*/ Enjoy,~ Shaun MerrillSeattle 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. |
|
|
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 MerrillSeattle area |
|
|
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 MerrillSeattle area |
|
|
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 seqFROM @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. |
|
|
|
|
|