DENSE_RANK 更改时按顺序中断

DENSE_RANK breaking sequentially when changed

grp 列应表示由 GroupId, RouteId 分区并排序 LengthStart 的块,如果 locationId 保持不变,则当您穿过块时 grp是一样的。序列中断会创建一个新的 grp

+---------+----------+--------------+------------+-------------+------+
| GROUPID |  ROUTEID |  LENGTHSTART |  LENGTHEND |  LOCATIONID |  GRP |
+---------+----------+--------------+------------+-------------+------+
|     1   | A        | 0            | 1          | 1           | 1    |
|     1   | A        | 1            | 2          | 1           | 1    |
|     1   | A        | 2            | 3          | 2           | 2    |
|     1   | A        | 3            | 4          | 1           | 3    |
|     2   | A        | 2            | 3          | 2           | 4    |
|     1   | B        | 2            | 3          | 2           | 5    |
|     1   | A        | 4            | 5          | 1           | 3    |
+---------+----------+--------------+------------+-------------+------+

我对这个问题的搜索使我找到了这个解决方案:

我尝试进行 grp 计算:

SELECT *, ROW_NUMBER() OVER (ORDER BY GroupId, RouteId, LengthStart) - ROW_NUMBER() OVER (PARTITION BY GroupId, RouteId, LocationId ORDER BY GroupId, RouteId, LengthStart) AS grp
FROM mytable

我尝试采用该解决方案,以便我可以进行更多级别的分区,并且它适用于真正基本的用例(如上面展示的那个,但在复杂的场景中它会失败)。

我真的不完全理解为什么两个 row_number() 相减以及它是如何计算的,但它在这个简单的例子中工作得很好。

我尝试了其他使用 LAG 的方法,但就是无法理解如何理解并应用它。

这里有一个fiddle,场景比较复杂:

https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=3704dfe8583b0dd020b189184d149cb7

您可以看到我在这里突出显示的许多错误之一:

这看起来像是一个空岛问题,您误解了解决方案。

在这种情况下,grp 列本身不会识别间隙或孤岛,它只会为您提供 extra 识别它们所需的信息。

在你的例子中,你实现它的方式 (locationId, grp) 形成了新的 "sub_group" 标识符。

我还会稍微更改您的查询,以便于理解正在发生的事情...

SELECT
  *,
    ROW_NUMBER() OVER (PARTITION BY GroupID, RouteID             ORDER BY LengthStart)
  - ROW_NUMBER() OVER (PARTITION BY GroupId, RouteId, LocationId ORDER BY LengthStart) AS grp
FROM
  mytable
ORDER BY
  GroupId, RouteId, LengthStart

这使得不同的 GroupID, RouteID 在计算差距和岛屿 (子组)

那我可以展示一个简化的例子...

  LocationID, Start, SetRowNum, LocRowNum, Difference, GroupID

      1        000       1       1          0          (1,0)
      1        100       2       2          0          (1,0)

      2        200       3         1          2        (2,2)
      2        300       4         2          2        (2,2)

      1        400       5       3          2          (1,2)

      2        500       6         3          3        (2,3)

      3        600       7           1          6      (3,6)

      2        700       8         4          4        (2,4)
      2        800       9         5          4        (2,4)

每个LocationID分别计算出一个island.

A gap 只是任何其他 LocationID 的任何行。

"trick" 是在每个岛内,两个行号一起增加。通过两者一起增加,差异保持不变。这个区别是 island_id.

然后,在间隙期间,第一个行号增加,这样当我们到达下一个岛时,两个行号之间的差异增加,给出一个新的 island_id **对于那个 LocationID.


请记住,在计算 location_1island_id 时,我们将 所有其他位置 视为 gaps 在位置 1 的岛屿之间。

对于位置 1,我们在 1,25 行有岛屿,IslandID 分别为 02

  LocationID, Start, SetRowNum, LocRowNum, Difference, GROUP_ID

      1        000       1       1          0          (1,0)
      1        100       2       2          0          (1,0)

      GAP

      1        400       5       3          2          (1,2)

对于位置 2,我们在 3,468,9 行有岛屿,其中 IslandID234分别。

  LocationID, Start, SetRowNum, LocRowNum, Difference

     GAP

      2        200       3         1          2        (2,2)
      2        300       4         2          2        (2,2)

     GAP

      2        500       6         3          3        (2,3)

     GAP

      2        700       8         4          4        (2,4)
      2        800       9         5          4        (2,4)

对于位置 3,我们在第 7 行有岛屿,其中 IslandID6

  LocationID, Start, SetRowNum, LocRowNum, Difference

     GAP

      3        600       7           1          6      (3,6)

     GAP

总而言之,这些岛屿都有不同的ID。

但是 Location1 和 Location2 都有 IslandID = 2 的岛屿。

  • 所以,你需要(locationID, IslandID)一起使用
  • 复合键对于该分区是唯一的

改变over子句的参数会产生不同的结果。请注意,您不需要按顺序重复用于分区的列。该技术的概念是计算一个公共值(在 grp 列中),该值经常用于后续计算,例如;步数,或成本,或 min/max 等

SELECT
    *
  , ROW_NUMBER() OVER (ORDER BY GroupId, RouteId, LengthStart) 
  - ROW_NUMBER() OVER (PARTITION BY GroupId, RouteId ORDER BY LengthStart ) AS grp
FROM mytable
ORDER BY
    GroupId
  , RouteId
  , LengthStart

输出两个 row_number 计算结果很有用,这样您就可以看到它是如何工作的:

    Id      GroupId   RouteId   LengthStart   LengthEnd   LocationId   rn1   rn2   grp  
 --------- --------- --------- ------------- ----------- ------------ ----- ----- ----- 
  2651246         3   AAA         0.0000000   0.0920000      1884268     1     1     0  
  2651247         3   AAA         0.0920000   0.5800000      1855305     2     2     0  
  2651248         3   AAA         0.5800000   1.3610000      1884268     3     3     0  
  2651249         3   AAA         1.3610000   1.6170000      1884268     4     4     0  
  2651250         3   AAA         1.6170000   2.3750000      1884268     5     5     0  
  2681493         3   BBB         0.0000000   1.5600000      1864963     6     1     5  
  2681494         3   BBB         1.5600000   2.7100000      1864963     7     2     5  
  2681495         3   BBB         2.7100000   3.3900000      1864963     8     3     5  
  2954915         3   CCC         0.0000000   0.0500000      1883382     9     1     8  
  2954916         3   CCC         0.0500000   0.1400000      1846300    10     2     8  
  …                                                                                     

rn1 列从 1 开始并不断递增。第二个 rn 列在每个分区中从 1 重新开始,但由于它也在相同的顺序序列上递增 1,因此当您从 rn1 中减去 rn2 时,您会得到 "constant" 结果 grp 用于 rn2

中使用的每个分区