Generate Row Number for every 3 rows

你离开我真会死。 提交于 2021-02-19 06:39:09

问题


I want generate number for every three rows

CREATE TABLE #test(period INT)

INSERT INTO #test
VALUES      (602),(603),(604),(605),(606),(607),(608),(609)

I know we can generate sequence using row_number window function or while loop or cursor

SELECT period,
       ( Row_number()OVER(ORDER BY period) - 1 ) / 3 + 1
FROM   #test 

Result;

+--------+-----+
| period | seq |
+--------+-----+
|    602 |   1 |
|    603 |   1 |
|    604 |   1 |
|    605 |   2 |
|    606 |   2 |
|    607 |   2 |
|    608 |   3 |
|    609 |   3 |
+--------+-----+

Is there any other way to achieve this mathematically. There will not be any gaps between the periods


回答1:


A mathematical or arithmetic approach could be to use the period numbers themselves:

-- table init here
DECLARE @MIN_PERIOD INT = (SELECT MIN(period) FROM #test)

SELECT period,
       (period - @MIN_PERIOD) / 3 + 1 AS seq
FROM   #test 

This works as long as "there will not be any gaps between the periods" remains true.

If you need a WHERE clause on the main query, also apply it to the SELECT MIN() query. Will work as long as the WHERE does not cause period gaps.




回答2:


It is possible to achieve this with the NTILE() function but I don't think that it is more efficient than ROW_NUMBER(), mainly because this method has to get the total count to determine the amount of groups.

Create test environment:

/*  -- SQL 2016
DROP TABLE IF EXISTS #test;
GO
*/

IF OBJECT_ID('tempdb.dbo.#test') IS NOT NULL DROP TABLE #test

CREATE TABLE #test(period INT);
GO

INSERT INTO #test -- Make it bigger
VALUES      (602),(603),(604),(605),(606),(607),(608),(609);
GO 51

ROW_NUMBER Method:

SELECT /*ROW_NUM*/ period,
       ( Row_number()OVER(ORDER BY period) - 1 ) / 3 + 1
FROM   #test;
GO

IO and Time performances: Shortened for readability

(400 row(s) affected) Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0 '#test_00000000000E'. Scan count 1, logical reads 1, physical reads 0

(1 row(s) affected)

SQL Server Execution Times: CPU time = 0 ms, elapsed time = 85 ms.

NTILE Method

DECLARE @ntile_var int;

SELECT @ntile_var = COUNT(*) FROM #test;

SELECT /*NTILE*/period
    , NTILE(@ntile_var / 3) OVER (ORDER BY period)
FROM #test

IO and Time performances: Shortened for readability

SQL Server parse and compile time: CPU time = 0 ms, elapsed time = 0 ms.

SQL Server Execution Times: CPU time = 0 ms, elapsed time = 0 ms.

SQL Server parse and compile time: CPU time = 0 ms, elapsed time = 0 ms.

Table '#test__00000000000E'. Scan count 1, logical reads 1, physical reads 0

(1 row(s) affected)

SQL Server Execution Times: CPU time = 0 ms, elapsed time = 0 ms.

(400 row(s) affected) Table 'Worktable'. Scan count 3, logical reads 811, physical reads 0 Table '#test___00000000000E'. Scan count 1, logical reads 1, physical reads 0

(1 row(s) affected)

SQL Server Execution Times: CPU time = 0 ms, elapsed time = 93

Both of these give the same results:

But there is a caveat! MSDN put it sufficiently as (emphasis added)

If the number of rows in a partition is not divisible by integer_expression, this will cause groups of two sizes that differ by one member. Larger groups come before smaller groups in the order specified by the OVER clause. For example if the total number of rows is 53 and the number of groups is five, the first three groups will have 11 rows and the two remaining groups will have 10 rows each.

So with the NTILE method, you could get a few groups of 4, so the rest of them can be 3.




回答3:


What about something like this...

WITH X AS (
SELECT *
     ,ROW_NUMBER() OVER (ORDER BY [period] ASC) rn 
FROM #test
 )
 SELECT [period]
      ,ROW_NUMBER() OVER (PARTITION BY (X.rn % 3) ORDER BY rn ASC) rn 
FROM X
ORDER BY [period]


来源:https://stackoverflow.com/questions/42137625/generate-row-number-for-every-3-rows

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!