Apa perbedaan antara LATERAL dan subquery di PostgreSQL?

146

Sejak Postgres keluar dengan kemampuan untuk LATERALbergabung, saya telah membacanya, karena saat ini saya melakukan dump data yang kompleks untuk tim saya dengan banyak subquery yang tidak efisien yang membuat keseluruhan permintaan membutuhkan waktu empat menit atau lebih.

Saya mengerti bahwa LATERALbergabung mungkin dapat membantu saya, tetapi bahkan setelah membaca artikel seperti ini dari Heap Analytics, saya masih belum cukup mengikuti.

Apa kasus penggunaan untuk LATERALbergabung? Apa perbedaan antara LATERALbergabung dan subquery?

jdotjdot
sumber
2
blog.heapanalytics.com/... dan menjelaskanextended.com/2009/07/16/inner-join-vs-cross-apply (SQL Server applysama dengan lateraldari standar SQL)
a_horse_with_no_name

Jawaban:

163

Lebih mirip subquery yang berkorelasi

Sebuah LATERALbergabung (Postgres 9.3 atau yang lebih baru) lebih seperti subquery berkorelasi , bukan subquery polos. Seperti yang ditunjukkan Andomar , fungsi atau subquery di sebelah kanan LATERALjoin harus dievaluasi satu kali untuk setiap baris di sebelah kiri - sama seperti subquery yang dikorelasikan - sementara subquery polos (ekspresi tabel) dievaluasi sekali saja. (Perencana permintaan memiliki cara untuk mengoptimalkan kinerja untuk keduanya.)
Jawaban terkait ini memiliki contoh kode untuk kedua sisi, menyelesaikan masalah yang sama:

Untuk mengembalikan lebih dari satu kolom , LATERALgabungan biasanya lebih sederhana, lebih bersih, dan lebih cepat.
Juga, ingat bahwa padanan subquery yang berkorelasi adalah LEFT JOIN LATERAL ... ON true:

Baca manual di LATERAL

Itu lebih berwibawa daripada apa pun yang akan kita jawab di sini:

Hal-hal yang tidak dapat dilakukan subquery

Ada hal - hal yang LATERALbisa dilakukan gabungan, tetapi subquery (berkorelasi) tidak bisa (dengan mudah). Subquery yang dikorelasikan hanya dapat mengembalikan nilai tunggal, bukan beberapa kolom dan bukan beberapa baris - dengan pengecualian panggilan fungsi kosong (yang mengalikan baris hasil jika menghasilkan banyak baris). Tetapi bahkan fungsi set-return tertentu hanya diperbolehkan dalam FROMklausa. Suka unnest()dengan banyak parameter di Postgres 9.4 atau lebih tinggi. Manual:

Ini hanya diperbolehkan dalam FROMklausa;

Jadi ini berfungsi, tetapi tidak dapat dengan mudah diganti dengan subquery:

CREATE TABLE tbl (a1 int[], a2 int[]);
SELECT * FROM tbl, unnest(a1, a2) u(elem1, elem2);  -- implicit LATERAL

Koma ( ,) dalam FROMklausa adalah notasi pendek untuk CROSS JOIN.
LATERALdiasumsikan secara otomatis untuk fungsi tabel.
Lebih lanjut tentang kasus khusus UNNEST( array_expression [, ... ] ):

Atur fungsi pengembalian dalam SELECTdaftar

Anda juga dapat menggunakan fungsi set-return seperti unnest()dalam SELECTdaftar secara langsung. Ini digunakan untuk memperlihatkan perilaku mengejutkan dengan lebih dari satu fungsi seperti itu dalam SELECTdaftar yang sama hingga Postgres 9.6. Tetapi akhirnya telah disanitasi dengan Postgres 10 dan merupakan alternatif yang valid sekarang (bahkan jika bukan SQL standar). Lihat:

Membangun contoh di atas:

SELECT *, unnest(a1) AS elem1, unnest(a2) AS elem2
FROM   tbl;

Perbandingan:

dbfiddle untuk hal 9.6 di sini
dbfiddle untuk hal 10 di sini

Jelaskan informasi yang salah

Manual:

Untuk tipe INNERdan OUTERgabung, syarat bergabung harus ditentukan, yaitu tepat satu NATURAL, ON join_condition , atau USING( join_column [, ...]). Lihat di bawah untuk maknanya.
Sebab CROSS JOIN, tidak satu pun dari klausa ini dapat muncul.

Jadi dua pertanyaan ini valid (bahkan jika tidak terlalu berguna):

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;

Sementara yang ini tidak:

SELECT *
FROM   tbl t
LEFT   JOIN LATERAL (SELECT * FROM b WHERE b.t_id = t.t_id) t;

Itu sebabnya @ Andomar ini contoh kode benar (yang CROSS JOINtidak memerlukan kondisi join) dan @ Attila ini yaitu tidak sah.

Erwin Brandstetter
sumber
Ada beberapa hal yang tidak dapat dilakukan oleh subquery yang BERGABUNG LATERAL. Suka fungsi jendela. Seperti di sini
Evan Carroll
@EvanCarroll: Saya tidak bisa menemukan subqueries yang terkait di tautan. Tetapi saya menambahkan jawaban lain untuk mendemonstrasikan fungsi jendela dalam sebuah LATERALsubquery: gis.stackexchange.com/a/230070/7244
Erwin Brandstetter
1
Lebih bersih dan lebih cepat? Seperti besarnya lebih cepat dalam beberapa kasus. Saya memiliki kueri yang berubah dari beberapa hari menjadi beberapa detik setelah beralih ke LATERAL.
rovyko
51

Perbedaan antara non- lateraldan lateralgabungan terletak pada apakah Anda dapat melihat ke baris tabel sebelah kiri. Sebagai contoh:

select  *
from    table1 t1
cross join lateral
        (
        select  *
        from    t2
        where   t1.col1 = t2.col1 -- Only allowed because of lateral
        ) sub

"Pandangan ke luar" ini berarti bahwa subquery harus dievaluasi lebih dari satu kali. Lagi pula, t1.col1dapat mengasumsikan banyak nilai.

Sebaliknya, subquery setelah non- lateraljoin dapat dievaluasi sekali:

select  *
from    table1 t1
cross join
        (
        select  *
        from    t2
        where   t2.col1 = 42 -- No reference to outer query
        ) sub

Seperti yang diperlukan tanpa lateral, permintaan dalam tidak tergantung dengan cara apa pun pada permintaan luar. Sebuah lateralpermintaan adalah contoh dari correlatedquery, karena hubungannya dengan baris di luar permintaan itu sendiri.

Andomar
sumber
5
Ini adalah penjelasan paling bersih dari lateral join.
1valdis
penjelasannya mudah dimengerti, terima kasih.
arilwan
Bagaimana cara select * from table1 left join t2 using (col1)membandingkan? Tidak jelas bagi saya ketika bergabung menggunakan / pada kondisi tidak cukup dan akan lebih masuk akal untuk menggunakan lateral.
No_name
9

Pertama, Lateral dan Cross Apply adalah hal yang sama . Karena itu, Anda juga dapat membaca tentang Lintas Aplikasi. Karena ini diimplementasikan dalam SQL Server selama berabad-abad, Anda akan menemukan informasi lebih lanjut tentang itu kemudian Lateral.

Kedua, menurut pemahaman saya , tidak ada yang tidak bisa Anda lakukan menggunakan subquery daripada menggunakan lateral. Tapi:

Pertimbangkan permintaan berikut.

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 

Anda dapat menggunakan lateral dalam kondisi ini.

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

Dalam kueri ini, Anda tidak dapat menggunakan gabungan normal, karena membatasi klausa. Lateral atau Cross Apply dapat digunakan ketika tidak ada kondisi gabung yang sederhana .

Ada lebih banyak penggunaan untuk menerapkan lateral atau lintas tetapi ini adalah yang paling umum yang saya temukan.

Atilla Ozgur
sumber
1
Tepat, saya bertanya-tanya mengapa PostgreSQL menggunakan lateralbukan apply. Mungkin Microsoft mematenkan sintaks?
Andomar
9
@Andomar AFAIK lateraldalam standar SQL tetapi applytidak.
mu terlalu pendek
The LEFT JOINmemerlukan kondisi join. Buat itu ON TRUEkecuali Anda ingin membatasi entah bagaimana.
Erwin Brandstetter
Erwin benar, Anda akan mendapatkan kesalahan kecuali jika Anda menggunakan cross joinatau suatu onkondisi
Andomar
1
@Andomar: Didorong oleh informasi yang salah ini saya menambahkan jawaban lain untuk mengklarifikasi.
Erwin Brandstetter
4

Satu hal yang tidak ada yang menunjukkan adalah Anda dapat menggunakan LATERALkueri untuk menerapkan fungsi yang ditentukan pengguna di setiap baris yang dipilih.

Misalnya:

CREATE OR REPLACE FUNCTION delete_company(companyId varchar(255))
RETURNS void AS $$
    BEGIN
        DELETE FROM company_settings WHERE "company_id"=company_id;
        DELETE FROM users WHERE "company_id"=companyId;
        DELETE FROM companies WHERE id=companyId;
    END; 
$$ LANGUAGE plpgsql;

SELECT * FROM (
    SELECT id, name, created_at FROM companies WHERE created_at < '2018-01-01'
) c, LATERAL delete_company(c.id);

Itulah satu-satunya cara saya tahu bagaimana melakukan hal semacam ini di PostgreSQL.

Theodore R. Smith
sumber