อะไรคือความแตกต่างระหว่าง LATERAL และแบบสอบถามย่อยใน PostgreSQL?


147

ตั้งแต่ Postgres ออกมาพร้อมกับความสามารถในการLATERALเข้าร่วมฉันได้อ่านมันเนื่องจากฉันกำลังทำการทิ้งข้อมูลที่ซับซ้อนสำหรับทีมของฉันพร้อมกับเคียวรีย่อยที่ไม่มีประสิทธิภาพจำนวนมากซึ่งทำให้เคียวรีโดยรวมใช้เวลาสี่นาทีหรือมากกว่า

ฉันเข้าใจว่าการLATERALเข้าร่วมอาจช่วยฉันได้ แต่แม้หลังจากอ่านบทความอย่างนี้จาก Heap Analytics ฉันก็ยังไม่ค่อยติดตาม

กรณีการใช้งานสำหรับการLATERALเข้าร่วมคืออะไร? การLATERALเข้าร่วมและการสืบค้นย่อยแตกต่างกันอย่างไร


2
blog.heapanalytics.com/... และexplainextended.com/2009/07/16/inner-join-vs-cross-apply (ของ SQL Server applyเป็นเช่นเดียวกับlateralมาตรฐานของ SQL)
a_horse_with_no_name

คำตอบ:


163

คล้ายกับแบบสอบถามย่อยที่มีความสัมพันธ์มากกว่า

การLATERALเข้าร่วม (Postgres 9.3 หรือใหม่กว่า) เป็นเหมือนแบบสอบถามย่อยที่มีความสัมพันธ์ไม่ใช่แบบสอบถามย่อยธรรมดา เช่นเดียวกับAndomar ชี้ให้เห็น , ฟังก์ชั่นหรือแบบสอบถามย่อยไปทางขวาของที่LATERALเข้าร่วมจะต้องมีการประเมินผลครั้งเดียวสำหรับแต่ละแถวด้านซ้ายของมัน - เช่นเดียวกับความสัมพันธ์แบบสอบถามย่อย - ในขณะที่แบบสอบถามย่อยธรรมดา (การแสดงออกของตาราง) คือการประเมินครั้งเดียวเท่านั้น (ผู้วางแผนคิวรีมีวิธีเพิ่มประสิทธิภาพสำหรับทั้งสองอย่าง)
คำตอบที่เกี่ยวข้องนี้มีตัวอย่างโค้ดสำหรับทั้งสองด้านเคียงข้างกันเพื่อแก้ปัญหาเดียวกัน:

สำหรับการส่งคืนมากกว่าหนึ่งคอลัมน์LATERALโดยทั่วไปการเข้าร่วมจะง่ายกว่าสะอาดกว่าและเร็วกว่า
นอกจากนี้โปรดจำไว้ว่าสิ่งที่เทียบเท่าของแบบสอบถามย่อยที่มีความสัมพันธ์คือLEFT JOIN LATERAL ... ON true:

อ่านคู่มือการใช้งาน LATERAL

มันมีอำนาจมากกว่าสิ่งใดที่เราจะตอบคำถามที่นี่:

สิ่งที่แบบสอบถามย่อยไม่สามารถทำได้

มีเป็นสิ่งที่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 [, ... ] )

ฟังก์ชั่น Set-return ในSELECTรายการ

นอกจากนี้คุณยังสามารถใช้ฟังก์ชั่นตั้งคืนเช่นเดียวกับunnest()ในSELECTรายการโดยตรง สิ่งนี้เคยแสดงพฤติกรรมที่น่าประหลาดใจกับฟังก์ชันดังกล่าวมากกว่าหนึ่งSELECTรายการในรายการเดียวกันจนถึง Postgres 9.6 แต่ในที่สุดมันก็ถูกทำให้สะอาดด้วย Postgres 10และเป็นทางเลือกที่ถูกต้องในตอนนี้ (แม้ว่าจะไม่ใช่ SQL มาตรฐาน) ดู:

ตัวอย่างอาคารด้านบน:

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

เปรียบเทียบ:

dbfiddle สำหรับ pg 9.6 ที่นี่
dbfiddle สำหรับ pg 10 ที่นี่

ชี้แจงข้อมูลที่ผิด

คู่มือ:

สำหรับประเภทINNERและOUTERประเภทการเข้าร่วมจะต้องระบุเงื่อนไขการเข้าร่วมนั่นคือหนึ่งในประเภททั้งหมดNATURAL, ON join_conditionหรือUSING( join_column [, ... ]) ดูความหมายด้านล่าง
สำหรับCROSS JOINคำสั่งเหล่านี้ไม่สามารถปรากฏได้

ดังนั้นข้อความค้นหาทั้งสองนี้จึงถูกต้อง (แม้ว่าจะไม่มีประโยชน์อย่างยิ่ง):

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 JOIN ไม่สามารถทำได้ ฟังก์ชั่นเหมือนหน้าต่าง อย่างที่นี่
Evan Carroll

@EvanCarroll: ฉันไม่พบข้อความค้นหาย่อยที่มีความสัมพันธ์ในลิงก์ แต่ฉันเพิ่มคำตอบอื่นเพื่อสาธิตฟังก์ชันหน้าต่างในLATERALแบบสอบถามย่อย: gis.stackexchange.com/a/230070/7244
Erwin Brandstetter

1
สะอาดและเร็วกว่านี้ไหม ชอบขนาดที่เร็วขึ้นในบางกรณี ฉันมีคำถามที่เปลี่ยนจากวันเป็นวินาทีหลังจากเปลี่ยนเป็น LATERAL
rovyko

52

ความแตกต่างระหว่างการที่ไม่ใช่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เข้าร่วมสามารถประเมินได้หนึ่งครั้ง:

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

ตามที่จำเป็นโดยไม่ต้องlateralแบบสอบถามภายในไม่ได้ขึ้นอยู่กับวิธีใด ๆ ในแบบสอบถามด้านนอก lateralแบบสอบถามเป็นตัวอย่างของหนึ่งcorrelatedแบบสอบถามเพราะความสัมพันธ์กับแถวนอกแบบสอบถามเอง


5
นี่คือคำอธิบายที่ชัดเจนที่สุดของการเข้าร่วมด้านข้าง
1valdis

คำอธิบายที่เข้าใจง่ายขอบคุณ
arilwan

วิธีการที่ไม่select * from table1 left join t2 using (col1)เปรียบเทียบ? มันไม่ชัดเจนสำหรับฉันเมื่อมีเงื่อนไขการเข้าร่วมการใช้ / เปิดไม่เพียงพอและจะใช้งานด้านข้างได้มากกว่า
No_name

9

ประการแรกLateral และ Cross Apply เป็นสิ่งเดียวกันสมัครเป็นสิ่งเดียวกัน ดังนั้นคุณอาจอ่านเกี่ยวกับ 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

ในแบบสอบถามนี้คุณไม่สามารถใช้การเข้าร่วมปกติได้เนื่องจากข้อ จำกัด ด้านข้างหรือข้าม Apply สามารถนำมาใช้เมื่อมีการเข้าร่วมไม่ง่ายสภาพ

มีการใช้งานมากขึ้นสำหรับการใช้ด้านข้างหรือการไขว้กัน แต่นี่เป็นสิ่งที่ฉันพบบ่อยที่สุด


1
ว่าผมสงสัยว่าทำไม PostgreSQL ใช้แทนlateral applyบางที Microsoft อาจจดสิทธิบัตรไวยากรณ์
Andomar

9
@Andomar AFAIK lateralอยู่ในมาตรฐาน SQL แต่applyไม่ใช่
mu สั้นเกินไป

LEFT JOINต้องใช้ร่วมสภาพ ทำมันON TRUEจนกว่าคุณจะต้องการ จำกัด อย่างใด
Erwin Brandstetter

เออร์วินพูดถูกคุณจะได้รับข้อผิดพลาดยกเว้นว่าคุณใช้เงื่อนไขcross joinหรือonเงื่อนไข
Andomar

1
@Andomar: กระตุ้นโดยข้อมูลที่ผิดนี้ฉันได้เพิ่มคำตอบเพื่อชี้แจง
Erwin Brandstetter

4

สิ่งหนึ่งที่ไม่มีใครชี้ให้เห็นคือคุณสามารถใช้LATERALคิวรีเพื่อใช้ฟังก์ชั่นที่ผู้ใช้กำหนดในทุกแถวที่เลือก

ตัวอย่างเช่น

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

นั่นเป็นวิธีเดียวที่ฉันรู้วิธีการทำสิ่งนี้ใน PostgreSQL

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.