Reputation: 13487
Please consider this script:
Declare @tbl Table
(
F1 int,
F2 int,
Year int,
Month tinyint
)
Insert into @tbl
values
(10, 1, 2020, 1),
(10, 1, 2020, 2),
(10, 1, 2020, 3),
(10, 1, 2020, 4),
(10, 2, 2020, 5),
(10, 1, 2020, 6),
(10, 1, 2020, 7),
(11, 1, 2020, 1),
(11, 1, 2020, 2),
(11, 2, 2020, 3),
(11, 2, 2020, 4),
(11, 1, 2020, 5),
(12, 1, 2020, 1),
(12, 1, 2020, 2),
(12, 1, 2020, 3),
(12, 1, 2020, 4)
I wrote this query:
Select
F1, F2, year, month,
Row_number() over (partition by F1, F2 order by year, month) as rownumber,
Rank() over (partition by F1, F2 order by year, month) as rnk
From
@tbl
Order by
f1, f2, year, month
This query returns this result set:
F1 | F2 | Year | Month | Rownumber | Rnk |
---|---|---|---|---|---|
10 | 1 | 2020 | 1 | 1 | 1 |
10 | 1 | 2020 | 2 | 2 | 2 |
10 | 1 | 2020 | 3 | 3 | 3 |
10 | 1 | 2020 | 4 | 4 | 4 |
10 | 1 | 2020 | 6 | 5 | 5 |
10 | 1 | 2020 | 7 | 6 | 6 |
10 | 2 | 2020 | 5 | 1 | 1 |
11 | 1 | 2020 | 1 | 1 | 1 |
11 | 1 | 2020 | 2 | 2 | 2 |
11 | 1 | 2020 | 5 | 3 | 3 |
11 | 2 | 2020 | 3 | 1 | 1 |
11 | 2 | 2020 | 4 | 2 | 2 |
12 | 1 | 2020 | 1 | 1 | 1 |
12 | 1 | 2020 | 2 | 2 | 2 |
12 | 1 | 2020 | 3 | 3 | 3 |
But I want this result:
F1 | F2 | Year | Month | Sequence |
---|---|---|---|---|
10 | 1 | 2020 | 1 | 1 |
10 | 1 | 2020 | 2 | 2 |
10 | 1 | 2020 | 3 | 3 |
10 | 1 | 2020 | 4 | 4 |
10 | 2 | 2020 | 5 | 1 |
10 | 1 | 2020 | 6 | 1 |
10 | 1 | 2020 | 7 | 2 |
11 | 1 | 2020 | 1 | 1 |
11 | 1 | 2020 | 2 | 2 |
11 | 2 | 2020 | 3 | 1 |
11 | 2 | 2020 | 4 | 2 |
11 | 1 | 2020 | 5 | 1 |
12 | 1 | 2020 | 1 | 1 |
12 | 1 | 2020 | 2 | 2 |
12 | 1 | 2020 | 3 | 3 |
12 | 1 | 2020 | 4 | 4 |
That is, in each set of the same F1 value,
sequence by year then month,
but reset the year month sequence number at each F2 change.
How can I achieve my desired result? Thanks
Upvotes: 0
Views: 107
Reputation: 227
WITH GroupedData
AS (SELECT F1,F2,YEAR,MONTH,
CASE
WHEN F2 != LAG(F2, 1, F2) OVER (PARTITION BY F1 ORDER BY YEAR, MONTH) THEN
1
ELSE
0
END AS IsNewGroup
FROM tbl),
Groups
AS (SELECT F1,F2,YEAR,MONTH,
SUM(IsNewGroup) OVER (PARTITION BY F1 ORDER BY YEAR, MONTH) AS GroupID
FROM GroupedData)
SELECT F1,F2,YEAR,MONTH,
ROW_NUMBER() OVER (PARTITION BY F1, GroupID ORDER BY YEAR, MONTH) AS SEQUENCE
FROM Groups
ORDER BY F1,YEAR,MONTH;
We use the LAG()
function to look at the F2 value of the previous row (within the same F1 group, ordered by Year and Month).
If the F2 value is different from the previous row, it means a new group is starting. In that case, we mark it with 1 (called IsNewGroup
). If it's the same, we mark it with 0.
We take the IsNewGroup
column and calculate a running total using SUM()
. This running total (called GroupID
) helps us assign a unique number to each group of rows that share the same F2 value within an F1 group.
For each group (based on F1 and GroupID), we generate a sequence number using ROW_NUMBER()
. This sequence is ordered by Year and Month.
Finally, we sort everything by F1, Year, and Month so the results appear in the correct order.
Upvotes: 1
Reputation: 522712
This is basically a gaps and islands problems in disguise. Here, each "island" is a group of records belonging to the same F1
block where the F2
value has not changed in sequence across year and month. We can generate a pseudo-group to keep track of these records, and then take the ROW_NUMBER()
to generate the final sequence values. Note that this pseudo-group value resets to 1 when either the F1
block ends or the F2
values changes.
WITH cte AS (
SELECT *, CASE WHEN LAG(F2) OVER (PARTITION BY F1 ORDER BY Year, Month) != F2 THEN 1 ELSE 0 END AS flag
FROM @tbl
),
cte2 AS (
SELECT *, SUM(flag) OVER (PARTITION BY F1 ORDER BY Year, Month) grp
FROM cte
)
SELECT F1, F2, Year, Month,
ROW_NUMBER() OVER (PARTITION BY F1, grp ORDER BY Year, Month) AS seq
FROM cte2
ORDER BY F1, Year, Month;
Upvotes: 3
Reputation: 1931
After having determined the borders (moments in the F1-grouped sequences where F2 changes), you can subtract the position of the last border from all subsequent positions:
With bordered as
(
Select
*,
Row_number() over win as pos,
case when F2 = lag(F2) over win then null else Count(1) over win - 1 end as ignore_before
From
@tbl
Window win As (partition by F1 order by year, month)
)
Select F1, F2, year, month, pos - max(ignore_before) over win Sequence
from bordered
Window win As (partition by F1 order by year, month)
Order by
f1, year, month;
Here it runs in an SQLFiddle.
As you'll want the windowed max of a windowed position,
you'll need at least one pass for each window function,
thus the necessary use of a CTE.
Note that I used a window definition to avoid repeating its use (and so, avoid copy errors).
Upvotes: 1