Sự khác biệt giữa LATITH và truy vấn con trong PostgreSQL là gì?


145

Vì Postgres ra đời với khả năng LATERALtham gia, tôi đã đọc nó, vì hiện tại tôi đang thực hiện các kết xuất dữ liệu phức tạp cho nhóm của mình với rất nhiều truy vấn không hiệu quả khiến cho truy vấn tổng thể mất bốn phút trở lên.

Tôi hiểu rằng việc LATERALtham gia có thể giúp tôi, nhưng ngay cả sau khi đọc các bài viết như thế này từ Heap Analytics, tôi vẫn không hoàn toàn theo dõi.

Trường hợp sử dụng cho một LATERALtham gia là gì? Sự khác biệt giữa LATERALtham gia và truy vấn con là gì?


2
blog.heapanalytics.com/...explainextended.com/2009/07/16/inner-join-vs-cross-apply (SQL Server applylà giống như lateraltừ các tiêu chuẩn SQL)
a_horse_with_no_name

Câu trả lời:


163

Giống như một truy vấn con tương quan

Tham LATERALgia (Postgres 9.3 trở lên) giống như một truy vấn con tương quan , không phải là truy vấn con đơn giản. Giống như Andomar đã chỉ ra , một hàm hoặc truy vấn con ở bên phải của phép LATERALnối phải được đánh giá một lần cho mỗi hàng bên trái của nó - giống như truy vấn con tương quan - trong khi truy vấn con đơn giản (biểu thức bảng) chỉ được đánh giá một lần . (Tuy nhiên, trình hoạch định truy vấn có các cách để tối ưu hóa hiệu suất cho cả hai.)
Câu trả lời liên quan này có các ví dụ mã cho cả hai bên, giải quyết cùng một vấn đề:

Để trả về nhiều hơn một cột , việc LATERALtham gia thường đơn giản hơn, sạch hơn và nhanh hơn.
Ngoài ra, hãy nhớ rằng tương đương với một truy vấn con tương quan là LEFT JOIN LATERAL ... ON true:

Đọc hướng dẫn trên LATERAL

Nó có thẩm quyền hơn bất cứ điều gì chúng ta sẽ đưa vào câu trả lời ở đây:

Những điều mà một truy vấn phụ không thể làm

những điều mà một LATERALtham gia có thể làm, nhưng một (tương quan) subquery có thể không (dễ dàng). Truy vấn con tương quan chỉ có thể trả về một giá trị duy nhất, không phải nhiều cột và không nhiều hàng - ngoại trừ các lệnh gọi hàm trần (sẽ nhân các hàng kết quả nếu chúng trả về nhiều hàng). Nhưng ngay cả các hàm nhất định, các hàm trả về chỉ được phép trong FROMmệnh đề. Giống như unnest()với nhiều tham số trong Postgres 9.4 trở lên. Hướng dẫn sử dụng:

Điều này chỉ được cho phép trong FROMđiều khoản;

Vì vậy, điều này hoạt động, nhưng không thể dễ dàng được thay thế bằng một truy vấn con:

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

Dấu phẩy ( ,) trong FROMmệnh đề là ký hiệu ngắn cho CROSS JOIN.
LATERALđược giả định tự động cho các chức năng bảng.
Thông tin thêm về trường hợp đặc biệt của UNNEST( array_expression [, ... ] ):

Các chức năng thiết lập trở lại trong SELECT danh sách

Bạn cũng có thể sử dụng các hàm trả về như unnest()trong SELECTdanh sách trực tiếp. Điều này được sử dụng để thể hiện hành vi đáng ngạc nhiên với nhiều hơn một chức năng như vậy trong cùng một SELECTdanh sách cho đến Postgres 9.6. Nhưng cuối cùng nó đã được khử trùng bằng Postgres 10 và là một thay thế hợp lệ ngay bây giờ (ngay cả khi không phải là SQL tiêu chuẩn). Xem:

Xây dựng trên ví dụ trên:

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

So sánh:

dbfiddle cho pg 9.6 tại đây
dbfiddle cho pg 10 tại đây

Làm rõ thông tin sai lệch

Hướng dẫn sử dụng:

Đối với các loại INNEROUTERtham gia, một điều kiện tham gia phải được chỉ định, cụ thể là một trong số đó NATURAL, ON tham gia_condition hoặc USING( tham gia [, ...]). Xem dưới đây cho ý nghĩa.
Đối với CROSS JOIN, không có điều khoản nào có thể xuất hiện.

Vì vậy, hai truy vấn này là hợp lệ (ngay cả khi không đặc biệt hữu ích):

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;

Trong khi cái này thì không:

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

Đó là lý do @ Andomar của mã ví dụ là đúng (những CROSS JOINkhông đòi hỏi một điều kiện tham gia) và @ Attila không hợp lệ.


Có một số điều mà một truy vấn con có thể làm THAM GIA THAM GIA không thể làm. Giống như các chức năng của cửa sổ. Như ở đây
Evan Carroll

@EvanCarroll: Tôi không thể tìm thấy bất kỳ truy vấn con tương quan nào trong liên kết. Nhưng tôi đã thêm một câu trả lời khác để thể hiện chức năng cửa sổ trong LATERALtruy vấn con: gis.stackexchange.com/a/230070/7244
Erwin Brandstetter

1
Sạch hơn và nhanh hơn? Giống như cường độ nhanh hơn trong một số trường hợp. Tôi đã có một truy vấn từ vài ngày đến vài giây sau khi chuyển sang LATITH.
rovyko

51

Sự khác biệt giữa không tham gia laterallateraltham gia nằm ở việc bạn có thể nhìn sang hàng của bàn bên trái hay không. Ví dụ:

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

Điều này "hướng ngoại" có nghĩa là truy vấn con phải được đánh giá nhiều lần. Rốt cuộc, t1.col1có thể giả định nhiều giá trị.

Ngược lại, truy vấn con sau khi không lateraltham gia có thể được đánh giá một lần:

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

Như được yêu cầu mà không có lateral, truy vấn bên trong không phụ thuộc vào bất kỳ cách nào vào truy vấn bên ngoài. Một lateraltruy vấn là một ví dụ về correlatedtruy vấn, vì mối quan hệ của nó với các hàng bên ngoài chính truy vấn đó.


5
Đây là lời giải thích rõ ràng nhất về tham gia bên.
1valdis

giải thích dễ hiểu, cảm ơn bạn.
arilwan

làm thế nào để select * from table1 left join t2 using (col1)so sánh? Tôi không rõ ràng khi điều kiện tham gia sử dụng / bật là không đủ và sẽ có ý nghĩa hơn khi sử dụng bên.
No_name

9

Đầu tiên, Lateral và Cross Apply là điều tương tự . Do đó, bạn cũng có thể đọc về Áp dụng chéo. Vì nó đã được triển khai trong SQL Server từ lâu, nên bạn sẽ tìm thấy nhiều thông tin hơn về nó sau đó là Lateral.

Thứ hai, theo sự hiểu biết của tôi , không có gì bạn không thể làm bằng cách sử dụng truy vấn con thay vì sử dụng bên. Nhưng:

Xem xét truy vấn sau đây.

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 

Bạn có thể sử dụng bên trong điều kiện này.

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

Trong truy vấn này, bạn không thể sử dụng nối bình thường, do mệnh đề giới hạn. Có thể sử dụng Lateral hoặc Cross Apply khi không có điều kiện nối đơn giản .

Có nhiều cách sử dụng cho ứng dụng bên hoặc chéo nhưng đây là cách sử dụng phổ biến nhất mà tôi tìm thấy.


1
Chính xác, tôi tự hỏi tại sao PostgreSQL sử dụng lateralthay vì apply. Có lẽ Microsoft đã cấp bằng sáng chế cú pháp?
Andomar

9
@Andomar AFAIK laterallà trong tiêu chuẩn SQL nhưng applykhông.
mu quá ngắn

Các LEFT JOINyêu cầu một điều kiện tham gia. Làm cho nó ON TRUEtrừ khi bạn muốn hạn chế bằng cách nào đó.
Erwin Brandstetter

Erwin đã đúng, bạn sẽ gặp lỗi trừ khi bạn sử dụng một cross joinhoặc một onđiều kiện
Andomar

1
@Andomar: Được thúc đẩy bởi thông tin sai lệch này, tôi đã thêm một câu trả lời khác để làm rõ.
Erwin Brandstetter

4

Một điều không ai chỉ ra là bạn có thể sử dụng LATERALcác truy vấn để áp dụng hàm do người dùng xác định trên mỗi hàng đã chọn.

Ví dụ:

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

Đó là cách duy nhất tôi biết cách thực hiện loại điều này trong PostgreSQL.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.