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
...
私たちが望むのは次のようなものです...(ここで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
...
私たちができるように
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
これは私の研究の答えで数回だけ出てきました。
このネットワークでは説明されたことがないけれども、明示的に他を混乱させているようです、
lag()
で解決および承認lag()
で承認済みを解決したがって、ここでのトリックは、アイランドを識別するために使用できる違いを生成する2つの等しく増分するシリーズのプロパティ_{11,12,13} - {1,2,3} = {10,10,10}
_です。このプロパティはそれ自体でアイランドを特定するのに十分ではありませんが、そうするために利用できる重要なステップです。
問題とは別に。これをチェックしてみましょう。
ここにいくつかのコードがあります。
_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)
_
x
とid
に加えて、ここにすべての変数が表示されます。
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()
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;
_
結果セット、dr
ense_rank、rn
row_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()
を使用してください。