web-dev-qa-db-ja.com

「ギャップとアイランド」をrow_number()とデンス_ランク()で解決しますか?

dense_rank()およびrow_number() を使用して gaps-and-islands のアイランド部分をどのように解決しますか。私はこれを数回見ましたが、誰かがそれを説明できるかどうか疑問に思っています、

データの例としてこのようなものを使用しましょう(例ではPostgreSQLを使用しています)。

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

これはこのようなものを生成するはずです。

 id | x 
----+---
  1 | 3
  2 | 1
  3 | 3
  4 | 3
  5 | 3
  6 | 2
  7 | 3
  8 | 2
  9 | 1
 10 | 3
...

私たちが望むのは次のようなものです...(ここでzGROUP 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
...

私たちができるように

  • GROUP BY x,grp(x,grp)は一意の島)
  • GROUP BY grpgrpは一意の島です。

上記の例では、(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

バックグラウンド

これは私の研究の答えで数回だけ出てきました。

このネットワークでは説明されたことがないけれども、明示的に他を混乱させているようです

5
Evan Carroll

したがって、ここでのトリックは、アイランドを識別するために使用できる違いを生成する2つの等しく増分するシリーズのプロパティ_{11,12,13} - {1,2,3} = {10,10,10}_です。このプロパティはそれ自体でアイランドを特定するのに十分ではありませんが、そうするために利用できる重要なステップです。

バックグラウンド

問題とは別に。これをチェックしてみましょう。

  • 1groupを3行として定義します。
  • Xoffsetsのオフセットのペア/行ごとに1つのgroupを作成します。

ここにいくつかのコードがあります。

_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)
_

これは見栄えが良く、この例ではdifferenceによるグループ化で十分です。 _(2,42)_、_(13,7)_、_(42,2)_で始まる3つのグループがあり、xoffsetsのグループに対応しています。これは本質的に逆の問題です。ただし、staticオフセットでこれを示しているため、majorの問題が1つあります。 2つのオフセット_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
_

2番目のオフセットを静的に定義する方法を見つける必要があります。

_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)
_

また、オフセットのペアごとにグループを作成することに戻りました。これは私たちがやっていることではありませんが、かなり近いです。うまくいけば、2つのセットを減算してアイランドを作成する方法を説明するのに役立ちます。

応用

ここで、テーブルfooを使用して上記の問題をもう一度見てみましょう。表示目的でのみ、xの2つのコピーの間に変数を挟みます。

_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)
_

xidに加えて、ここにすべての変数が表示されます。

  • lhsは単純です。これで一意のシーケンシャル識別子を生成しているだけです(一意のシーケンシャル識別子を提供しているためです:id-決して忘れないでください idはめったにギャップがない
  • rhsは少し複雑です。 xで分割し、異なるx値の中でそれを使用して順次識別子を生成します。すでに表示されている値を持つ行が表示されるたびに、セットでrhsがどのように増加するかを確認します。 rhsのプロパティは、値が表示された回数です。
  • diffは減算の単純な結果ですが、そのように考えることはあまり役に立ちません。数字ではなくシーケンスを引くようなものだと考えてください(ただし、それらは単一の行の数字です)。値が表示される回数だけ1ずつ増加するシーケンスがあります。そして、(毎回)異なるIDごとに1ずつ増加するシーケンスがあります。これらの2つのシーケンスを減算すると、上記の例のように、繰り返し値に対して同じ数が返されます。 _(11,12,13) - (1,2,3) = (10,10,10)_。これは、この回答の最初のセクションと同じ原理です。
    • diffは単独でグループにマークを付けません
    • diffは、xのすべての同一のアイランドを強制的にグループ化します(たとえば、上記の_diff=3_の3つのケースと対応するx値の誤検知がある場合があります) )

グループgrpは_(x, diff)_の関数です。少し変な形式ですが、グループ化IDとして機能します。これは、diffでグループ化した場合justに発生する誤検知を減らすのに役立ちます。

単純な最適化されていないクエリ

これで、最適化されていない単純なクエリができました。

_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;
_

最適化

dense_rank()row_number()のような別のものに置き換えることについて。 @ ypercubeがコメント

ROW_NUMBER()でも機能しますが、データによって結果が異なる場合があると思います。テーブルに重複するデータがあるかどうかによって異なります。

復習しましょう。ここに示すクエリは

  • row_number()およびdense_rank()
  • 計算されたそれぞれの差異。
  • id、_1,2,3,4,5,6,7,8,6,7,8_、およびx valsの2つの異なる「アイランド」を含む結果セット。

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;
_

結果セット、drense_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
_

ここで、注文している列が重複している場合、重複しているクエリの順序を確保できないことができないため、このメソッドを使用できないことがわかります。 _ORDER BY_句内。違いを生み出すのは、複製の順序付けの影響だけです。パーティション全体の増分値に対するグローバルインクリメーティング値の関係です。ただし、一意のid列、または一意性を定義する一連の列がある場合は、必ずrow_number()の代わりにuse dense_rank()を使用してください。

9
Evan Carroll