220

Postgres がLATERALジョインを実行できるようになったので、私はそれについて調べてきました。現在、私は現在、クエリ全体に 4 分以上かかる非効率なサブクエリを多数使用して、チームのために複雑なデータ ダンプを実行しているためです。

LATERAL結合が役立つ可能性があることは理解していますが、Heap Analytics のこのような記事を読んだ後でも、まだ理解できていません。

LATERAL結合の使用例は何ですか? LATERAL結合とサブクエリの違いは何ですか?

4

5 に答える 5

249

結合と何ですか?LATERAL

この機能は PostgreSQL 9.3 で導入されました。マニュアル:

に現れるサブクエリFROMの前にキーワード を付けることができます LATERALFROMこれにより、前の項目で提供された列を参照できます 。( がない場合、各サブクエリは個別に評価されるため、他の項目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結合タイプの場合、結合条件を指定する必要があります。つまり、、NATURALjoin_condition ON または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のコード例はそう ではありませんでした。

于 2015-02-17T08:40:58.763 に答える
92

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

于 2015-02-16T22:16:49.763 に答える
20

データベース テーブル

blogプラットフォームでホストされているブログを格納する次のデータベース テーブルを用意します。

ブログテーブル

また、現在ホストされている 2 つのブログがあります。

ID 作成日 題名 URL
1 2013-09-30 Vlad Mihalcea のブログ https://vladmihalcea.com
2 2017-01-22 過敏症 https://hypersistence.io

SQL LATERAL JOIN を使用せずにレポートを取得する

blogテーブルから次のデータを抽出するレポートを作成する必要があります。

  • ブログID
  • ブログの年齢 (年)
  • 次のブログ記念日の日付
  • 次の記念日までの残り日数。

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_anniversarydays_to_next_anniversary

そして、それがまさに LATERAL JOIN が私たちを助けることができるところです。

SQL LATERAL JOIN を使用してレポートを取得する

次のリレーショナル データベース システムは、LATERAL JOIN構文をサポートしています。

  • 12c以降のオラクル
  • PostgreSQL 9.3 以降
  • 8.0.14 以降の MySQL

SQL Server はとLATERAL JOINを使用してCROSS APPLYエミュレートできますOUTER APPLY

LATERAL JOIN を使用すると、値を再利用して、値age_in_yearsを計算するときにそれをさらに渡すことができます。next_anniversarydays_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ます。したがって、相関サブクエリのように機能しますが、サブクエリ レコードはプライマリ テーブルと結合されるため、サブクエリによって生成された列を参照できます。

于 2021-01-22T15:01:21.487 に答える
14

まず、ラテラルとクロス適用は同じものです。したがって、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 を使用できます。

ラテラルまたはクロス適用にはさらに多くの使用法がありますが、これは私が見つけた最も一般的なものです。

于 2015-02-16T21:51:40.990 に答える