Postgres がLATERAL
ジョインを実行できるようになったので、私はそれについて調べてきました。現在、私は現在、クエリ全体に 4 分以上かかる非効率なサブクエリを多数使用して、チームのために複雑なデータ ダンプを実行しているためです。
LATERAL
結合が役立つ可能性があることは理解していますが、Heap Analytics のこのような記事を読んだ後でも、まだ理解できていません。
LATERAL
結合の使用例は何ですか? LATERAL
結合とサブクエリの違いは何ですか?
Postgres がLATERAL
ジョインを実行できるようになったので、私はそれについて調べてきました。現在、私は現在、クエリ全体に 4 分以上かかる非効率なサブクエリを多数使用して、チームのために複雑なデータ ダンプを実行しているためです。
LATERAL
結合が役立つ可能性があることは理解していますが、Heap Analytics のこのような記事を読んだ後でも、まだ理解できていません。
LATERAL
結合の使用例は何ですか? LATERAL
結合とサブクエリの違いは何ですか?
LATERAL
この機能は PostgreSQL 9.3 で導入されました。マニュアル:
に現れるサブクエリ
FROM
の前にキーワード を付けることができますLATERAL
。FROM
これにより、前の項目で提供された列を参照できます 。( がない場合、各サブクエリは個別に評価されるため、他の項目LATERAL
を相互参照できません。)FROM
に現れるテーブル関数
FROM
の前にキーワード を付けることもできますLATERAL
が、関数の場合、キーワードはオプションです。関数の引数にはFROM
、いずれの場合でも、前の項目によって提供される列への参照を含めることができます。
そこには基本的なコード例が示されています。
LATERAL
結合は、単純なサブクエリではなく相関サブクエリに似ています。LATERAL
結合の右側の式は、その左の行ごとに 1 回評価されます (相関サブクエリと同様)。一方、単純なサブクエリ (テーブル式) は1 回評価されます。それだけ。(ただし、クエリプランナーには、どちらのパフォーマンスも最適化する方法があります。)
同じ問題を解決する、両方のコード例を並べた関連する回答:
複数の列を返す場合、LATERAL
結合は通常、よりシンプルでクリーンで高速です。
また、相関サブクエリに相当するものは次のLEFT JOIN LATERAL ... ON true
とおりです。
結合でできることはありますLATERAL
が、(相関) サブクエリでは (簡単に) できません。相関サブクエリは単一の値のみを返すことができ、複数の列や複数の行ではなく、ただの関数呼び出し (複数の行を返す場合に結果の行を乗算する) を除きます。FROM
ただし、特定のセットを返す関数でさえ、句でのみ使用できます。unnest()
Postgres 9.4 以降の複数のパラメーターと同様です。マニュアル:
これは
FROM
句でのみ許可されます。
したがって、これは機能しますが、(簡単に) サブクエリに置き換えることはできません。
CREATE TABLE tbl (a1 int[], a2 int[]);
SELECT * FROM tbl, unnest(a1, a2) u(elem1, elem2); -- implicit LATERAL
節のコンマ ( ,
)FROM
は の短縮表記ですCROSS JOIN
。
LATERAL
テーブル関数では自動的に想定されます。
の特殊なケースについてUNNEST( array_expression [, ... ] )
:
SELECT
リスト内の集合を返す関数リストunnest()
のようなセットを返す関数を直接使用することもできます。これは、 Postgres 9.6 まで、同じリストSELECT
に複数のそのような関数があると驚くべき動作を示していました。しかし、最終的に Postgres 10 でサニタイズされ、 (標準 SQL でなくても) 有効な代替手段になりました。見る:SELECT
上記の例に基づいて構築:
SELECT *, unnest(a1) AS elem1, unnest(a2) AS elem2
FROM tbl;
比較:
pg 9.6 のdbfiddle はこちら
pg 10 の dbfiddle はこちら
INNER
およびOUTER
結合タイプの場合、結合条件を指定する必要があります。つまり、、NATURAL
join_conditionON
、またはUSING
( join_column [, ...]) のいずれかを指定する必要があります。意味は下記参照。
の場合CROSS JOIN
、これらの句はいずれも使用できません。
したがって、これら 2 つのクエリは有効です (特に有用ではありませんが)。
SELECT *
FROM tbl t
LEFT JOIN LATERAL (SELECT * FROM b WHERE b.t_id = t.t_id) t ON TRUE;
SELECT *
FROM tbl t, LATERAL (SELECT * FROM b WHERE b.t_id = t.t_id) t;
これはそうではありませんが:
SELECT * FROM tbl t LEFT JOIN LATERAL (SELECT * FROM b WHERE b.t_id = t.t_id) t;
そのため、Andomar のコード例は正しく ( はCROSS JOIN
結合条件を必要としません)、Attilaのコード例はそう ではありませんでした。
lateral
非結合と結合の違いはlateral
、左側のテーブルの行を参照できるかどうかにあります。例えば:
select *
from table1 t1
cross join lateral
(
select *
from t2
where t1.col1 = t2.col1 -- Only allowed because of lateral
) sub
この「外向き」は、サブクエリを複数回評価する必要があることを意味します。結局のところ、t1.col1
多くの値を想定できます。
対照的に、非lateral
結合後のサブクエリは 1 回評価できます。
select *
from table1 t1
cross join
(
select *
from t2
where t2.col1 = 42 -- No reference to outer query
) sub
なしで必要とされるlateral
ように、内側のクエリは外側のクエリにまったく依存しません。クエリは、クエリ自体の外部の行との関係があるため、クエリのlateral
一例です。correlated
blog
プラットフォームでホストされているブログを格納する次のデータベース テーブルを用意します。
また、現在ホストされている 2 つのブログがあります。
ID | 作成日 | 題名 | URL |
---|---|---|---|
1 | 2013-09-30 | Vlad Mihalcea のブログ | https://vladmihalcea.com |
2 | 2017-01-22 | 過敏症 | https://hypersistence.io |
blog
テーブルから次のデータを抽出するレポートを作成する必要があります。
PostgreSQL を使用している場合は、次の SQL クエリを実行する必要があります。
SELECT
b.id as blog_id,
extract(
YEAR FROM age(now(), b.created_on)
) AS age_in_years,
date(
created_on + (
extract(YEAR FROM age(now(), b.created_on)) + 1
) * interval '1 year'
) AS next_anniversary,
date(
created_on + (
extract(YEAR FROM age(now(), b.created_on)) + 1
) * interval '1 year'
) - date(now()) AS days_to_next_anniversary
FROM blog b
ORDER BY blog_id
ご覧のとおり、との値age_in_years
を計算するときに が必要になるため、 を 3 回定義する必要があります。next_anniversary
days_to_next_anniversary
そして、それがまさに LATERAL JOIN が私たちを助けることができるところです。
次のリレーショナル データベース システムは、LATERAL JOIN
構文をサポートしています。
SQL Server はとLATERAL JOIN
を使用してCROSS APPLY
エミュレートできますOUTER APPLY
。
LATERAL JOIN を使用すると、値を再利用して、値age_in_years
を計算するときにそれをさらに渡すことができます。next_anniversary
days_to_next_anniversary
前のクエリは、次のように LATERAL JOIN を使用するように書き直すことができます。
SELECT
b.id as blog_id,
age_in_years,
date(
created_on + (age_in_years + 1) * interval '1 year'
) AS next_anniversary,
date(
created_on + (age_in_years + 1) * interval '1 year'
) - date(now()) AS days_to_next_anniversary
FROM blog b
CROSS JOIN LATERAL (
SELECT
cast(
extract(YEAR FROM age(now(), b.created_on)) AS int
) AS age_in_years
) AS t
ORDER BY blog_id
また、age_in_years
値を計算して、next_anniversary
およびのdays_to_next_anniversary
計算に再利用できます。
blog_id | age_in_years | next_anniversary | days_to_next_anniversary |
---|---|---|---|
1 | 7 | 2021-09-30 | 295 |
2 | 3 | 2021-01-22 | 44 |
はるかに良いですよね?
はage_in_years
、テーブルのすべてのレコードに対して計算されblog
ます。したがって、相関サブクエリのように機能しますが、サブクエリ レコードはプライマリ テーブルと結合されるため、サブクエリによって生成された列を参照できます。
まず、ラテラルとクロス適用は同じものです。したがって、Cross Apply についても読むことができます。これは長い間 SQL Server に実装されていたため、Lateral よりも詳細な情報を見つけることができます。
第二に、私の理解によれば、ラテラルを使用する代わりにサブクエリを使用してできないことは何もありません。しかし:
次のクエリを検討してください。
Select A.*
, (Select B.Column1 from B where B.Fk1 = A.PK and Limit 1)
, (Select B.Column2 from B where B.Fk1 = A.PK and Limit 1)
FROM A
この状態でラテラルが使えます。
Select A.*
, x.Column1
, x.Column2
FROM A LEFT JOIN LATERAL (
Select B.Column1,B.Column2,B.Fk1 from B Limit 1
) x ON X.Fk1 = A.PK
このクエリでは、制限句のため、通常の結合を使用できません。単純な結合条件がない場合は、 Lateral または Cross Apply を使用できます。
ラテラルまたはクロス適用にはさらに多くの使用法がありますが、これは私が見つけた最も一般的なものです。