用row_number() 和dense_rank() 解决“差距和孤岛”?

Eva*_*oll 9 postgresql window-functions gaps-and-islands rank

如何用解决的孤岛部分。我现在已经看过几次了,我想知道是否有人可以解释一下,dense_rank()row_number()

让我们使用这样的东西作为示例数据(示例使用 PostgreSQL),

CREATE TABLE foo
AS
  SELECT x AS id, trunc(random()*3+1) AS x
  FROM generate_series(1,50)
    AS t(x);
Run Code Online (Sandbox Code Playgroud)

这应该产生这样的东西。

 id | x 
----+---
  1 | 3
  2 | 1
  3 | 3
  4 | 3
  5 | 3
  6 | 2
  7 | 3
  8 | 2
  9 | 1
 10 | 3
...
Run Code Online (Sandbox Code Playgroud)

我们想要的是这样的...... z我们可以使用的价值在哪里GROUP BY

 id | x | grp
----+------
  1 | 3 | z
  2 | 1 | z
  3 | 3 | z
  4 | 3 | z
  5 | 3 | z
  6 | 2 | z
  7 | 3 | z
  8 | 2 | z
  9 | 1 | z
 10 | 3 | z
...
Run Code Online (Sandbox Code Playgroud)

这样我们可以做任何一个

  • GROUP BY x,grp哪里(x,grp)有独特的岛屿
  • GROUP BY grp哪里grp是一个独特的岛屿。

在上面的例子中,(x,grp) 看起来像这样.. z我们可以使用的值在哪里GROUP BY

 id | x | grp
----+------
  1 | 3 | 1
  2 | 1 | 1
  3 | 3 | 2
  4 | 3 | 2
  5 | 3 | 2
  6 | 2 | z    -- Because we're grouping by an (x,grp)
  7 | 3 | z    -- Does not have to be `3` (or something unique)
  8 | 2 | z    -- z=1, is fine to use again if we 
  9 | 1 | z    -- GROUP BY (x,grp)
 10 | 3 | z
Run Code Online (Sandbox Code Playgroud)

背景

在我的研究中,这仅在答案中出现过几次。

它似乎也明确地混淆了其他人,尽管它从未在这个网络上解释过

Eva*_*oll 11

所以这里的技巧是两个等量递增系列的属性,它们产生可用于识别岛屿的差异{11,12,13} - {1,2,3} = {10,10,10}。此属性不足以识别岛屿本身,但这是我们可以利用的关键步骤。

背景

回避问题。让我们检查一下..在这里我们

  • 将 1定义为 3 行。
  • 为 xoffsets 中的每一对/每行偏移创建一个

这是一些代码。

SELECT x1, x2, x1-x2 AS difference
FROM (VALUES
  (2,42),
  (13,7),
  (42,2)
)
  AS xoffsets(o1,o2)
CROSS JOIN LATERAL (
  SELECT x+o1,x+o2  -- here we add the offsets above to x
  FROM generate_series(1,3) AS t(x)
) AS t(x1, x2)
ORDER BY x1, x2;

 x1 | x2 | difference 
----+----+------------
  3 | 43 |        -40
  4 | 44 |        -40
  5 | 45 |        -40
 14 |  8 |          6
 15 |  9 |          6
 16 | 10 |          6
 43 |  3 |         40
 44 |  4 |         40
 45 |  5 |         40
(9 rows)
Run Code Online (Sandbox Code Playgroud)

这看起来很不错,difference在这个例子中按 分组就足够了。你可以看到我们在开始三组 (2,42)(13,7)以及(42,2)对应的组xoffsets。这本质上是相反的问题。但是我们有一个主要问题,因为我们正在用静态偏移来证明这一点。如果任何两个偏移量之间的差异o1-o2相同,我们就会遇到问题。像这样,

SELECT x1, x2, x1-x2 AS difference
FROM (VALUES
  (100,90),
  (90,80)
) AS xoffsets(o1,o2)
CROSS JOIN LATERAL (
  SELECT x+o1,x+o2  -- here we add the offsets above to x
  FROM generate_series(1,3) AS t(x)
) AS t(x1, x2)
ORDER BY x1, x2;

 x1  | x2 | difference 
-----+----+------------
  91 | 81 |         10
  92 | 82 |         10
  93 | 83 |         10
 101 | 91 |         10
 102 | 92 |         10
 103 | 93 |         10
Run Code Online (Sandbox Code Playgroud)

我们必须找到一种方法来静态定义第二个偏移量。

SELECT x1, x2, x1-x2 AS difference
FROM (VALUES
  (100,0),
  (90,0)
) AS xoffsets(o1,o2)
CROSS JOIN LATERAL (
  SELECT x+o1,x+o2  -- here we add the offsets above to x
  FROM generate_series(1,3) AS t(x)
) AS t(x1, x2)
ORDER BY x1, x2;

 x1  | x2 | difference 
-----+----+------------
  91 |  1 |         90
  92 |  2 |         90
  93 |  3 |         90
 101 |  1 |        100
 102 |  2 |        100
 103 |  3 |        100
(6 rows)
Run Code Online (Sandbox Code Playgroud)

而且,我们再次回到为每对偏移量创建组的轨道上。这不是我们正在做的,但它非常接近,希望它有助于说明如何减去这两组来创建岛屿。

应用

现在让我们用 table 重新审视上面的问题foo。我们将变量夹在两个副本之间x仅用于显示目的。

SELECT
  id,
  x,
  dense_rank() OVER (w1) AS lhs,
  dense_rank() OVER (w2) AS rhs,
  dense_rank() OVER (w1)
    - dense_rank() OVER (w2)
    AS diff,
  (
    x,
    dense_rank() OVER (w1)
      - dense_rank() OVER (w2)
  ) AS grp,
  x
FROM foo
WINDOW w1 AS (ORDER BY id),
  w2 AS (PARTITION BY x ORDER BY id)
ORDER BY id
LIMIT 10;


 id | x | lhs | rhs | diff |  grp  | x 
----+---+-----+-----+------+-------+---
  1 | 2 |   1 |   1 |    0 | (2,0) | 2
  2 | 1 |   2 |   1 |    1 | (1,1) | 1
  3 | 2 |   3 |   2 |    1 | (2,1) | 2
  4 | 3 |   4 |   1 |    3 | (3,3) | 3
  5 | 3 |   5 |   2 |    3 | (3,3) | 3
  6 | 2 |   6 |   3 |    3 | (2,3) | 2
  7 | 3 |   7 |   3 |    4 | (3,4) | 3
  8 | 1 |   8 |   2 |    6 | (1,6) | 1
  9 | 3 |   9 |   4 |    5 | (3,5) | 3
 10 | 1 |  10 |   3 |    7 | (1,7) | 1
(10 rows)
Run Code Online (Sandbox Code Playgroud)

除了xid

  • lhs是简单的。我们只是用它生成一个唯一的顺序标识符(因为我们给它提供了一个唯一的顺序标识符:id--尽管永远不要忘记ids 很少是无缝的
  • rhs稍微复杂的。我们x根据不同的x值划分并生成一个顺序标识符。观察rhs每次看到具有它已经看到的值的行时如何在集合上递增。的属性rhs值被看到的次数
  • diff是减法的简单结果,但这样想并没有太大用处。想想更像是减去一个序列而不是数字(尽管它们是任何单行的数字)。我们有一个序列,随着一个值出现的次数增加 1。并且,对于每个不同的 id(每次),我们都有一个递增 1 的序列。减去这两个序列将返回相同的重复值数字,就像我们上面的例子一样。(11,12,13) - (1,2,3) = (10,10,10). 这与此答案的第一部分中的原理相同
    • diff 不单独标记组本身
    • diff强制所有相同的岛x归入一个组(可能有误报,例如上述三种情况diff=3及其对应的x值)

grp是 的函数(x, diff)。它用作分组 id,尽管格式有点奇怪。这有助于减少如果我们只是按差异分组会发生的误报。

简单的未优化查询

所以现在我们有了简单的未优化查询

SELECT x, diff, count(*)
FROM (
  SELECT
    id,
    x,
    dense_rank() OVER (ORDER BY id)
      - dense_rank() OVER (PARTITION BY x ORDER BY id)
    AS diff
  FROM foo
) AS t
GROUP BY x, diff;
Run Code Online (Sandbox Code Playgroud)

优化

关于dense_rank()用其他东西替换的问题,比如row_number(). @ypercube 评论

它也适用于 ROW_NUMBER() 但我认为它可能会使用不同的数据给出不同的结果。取决于表中是否有重复数据。

所以让我们回顾一下,这是一个查询,显示

  • row_number()dense_rank()
  • 计算出的各自差异。
  • 在包括 ids1,2,3,4,5,6,7,8,6,7,8xvals 的两个不同“孤岛”的结果集上。

SQL查询,

SELECT
  id,
  x,
  dense_rank() OVER (w1) AS dr_w1,
  dense_rank() OVER (w2) AS dr_w2,
  (
    x,
    dense_rank() OVER (w1)
    - dense_rank() OVER (w2)
  ) AS dense_diffgrp,
  row_number() OVER (w1) AS rn_w1,
  row_number() OVER (w2) AS rn_w2,
  (
    x,
    row_number() OVER (w1)
    - row_number() OVER (w2)
  ) AS rn_diffgrp,
  x
FROM (
  SELECT id,
  CASE WHEN id<4
    THEN 1
    ELSE 0
  END
  FROM
  (
    SELECT * FROM generate_series(1,8)
    UNION ALL
    SELECT * FROM generate_series(6,8)
  ) AS t(id)
) AS t(id,x)
WINDOW w1 AS (ORDER BY id),
  w2 AS (PARTITION BY x ORDER BY id)
ORDER BY id;
Run Code Online (Sandbox Code Playgroud)

结果集、drdense_rank、rnrow_number

 id | x | dr_w1 | dr_w2 | dense_diffgrp | rn_w1 | rn_w2 | rn_diffgrp | x 
----+---+-------+-------+---------------+-------+-------+------------+---
  1 | 1 |     1 |     1 | (1,0)         |     1 |     1 | (1,0)      | 1
  2 | 1 |     2 |     2 | (1,0)         |     2 |     2 | (1,0)      | 1
  3 | 1 |     3 |     3 | (1,0)         |     3 |     3 | (1,0)      | 1
  4 | 0 |     4 |     1 | (0,3)         |     4 |     1 | (0,3)      | 0
  5 | 0 |     5 |     2 | (0,3)         |     5 |     2 | (0,3)      | 0
  6 | 0 |     6 |     3 | (0,3)         |     7 |     3 | (0,4)      | 0
  6 | 0 |     6 |     3 | (0,3)         |     6 |     4 | (0,2)      | 0
  7 | 0 |     7 |     4 | (0,3)         |     8 |     6 | (0,2)      | 0
  7 | 0 |     7 |     4 | (0,3)         |     9 |     5 | (0,4)      | 0
  8 | 0 |     8 |     5 | (0,3)         |    10 |     8 | (0,2)      | 0
  8 | 0 |     8 |     5 | (0,3)         |    11 |     7 | (0,4)      | 0
Run Code Online (Sandbox Code Playgroud)

您将在此处看到,当您排序的列具有重复项时,您无法使用此方法,因为您无法确保ORDER BY子句中具有重复项的查询的排序。很大程度上只是重复排序的影响导致了差异:全局递增值与分区上递增值的关系。但是,当您有一个唯一的 id 列或一系列定义唯一性的列时,请务必使用row_number()代替dense_rank()