ฉันมีตารางต่อไปนี้ (นำมาจากฐานข้อมูล Sakila):
- film: film_id คือ pkey
- นักแสดง: actor_id คือกุญแจ
- film_actor: film_id และ actor_id เป็น fkeys สำหรับภาพยนตร์ / นักแสดง
ฉันกำลังเลือกภาพยนตร์เฉพาะเรื่อง สำหรับหนังเรื่องนี้ฉันต้องการให้นักแสดงทุกคนเข้าร่วมในภาพยนตร์เรื่องนี้ด้วย ฉันมีสองคำสั่งสำหรับการนี้: หนึ่งที่มีและเป็นหนึ่งเดียวกับLEFT JOIN
LEFT JOIN LATERAL
select film.film_id, film.title, a.actors
from film
left join
(
select film_actor.film_id, array_agg(first_name) as actors
from actor
inner join film_actor using(actor_id)
group by film_actor.film_id
) as a
on a.film_id = film.film_id
where film.title = 'ACADEMY DINOSAUR'
order by film.title;
select film.film_id, film.title, a.actors
from film
left join lateral
(
select array_agg(first_name) as actors
from actor
inner join film_actor using(actor_id)
where film_actor.film_id = film.film_id
) as a
on true
where film.title = 'ACADEMY DINOSAUR'
order by film.title;
เมื่อเปรียบเทียบแผนแบบสอบถามคิวรีแรกจะทำงานได้แย่กว่ามาก (20x) กว่าวินาที:
Merge Left Join (cost=507.20..573.11 rows=1 width=51) (actual time=15.087..15.089 rows=1 loops=1)
Merge Cond: (film.film_id = film_actor.film_id)
-> Sort (cost=8.30..8.31 rows=1 width=19) (actual time=0.075..0.075 rows=1 loops=1)
Sort Key: film.film_id
Sort Method: quicksort Memory: 25kB
-> Index Scan using idx_title on film (cost=0.28..8.29 rows=1 width=19) (actual time=0.044..0.058 rows=1 loops=1)
Index Cond: ((title)::text = 'ACADEMY DINOSAUR'::text)
-> GroupAggregate (cost=498.90..552.33 rows=997 width=34) (actual time=15.004..15.004 rows=1 loops=1)
Group Key: film_actor.film_id
-> Sort (cost=498.90..512.55 rows=5462 width=8) (actual time=14.934..14.937 rows=11 loops=1)
Sort Key: film_actor.film_id
Sort Method: quicksort Memory: 449kB
-> Hash Join (cost=6.50..159.84 rows=5462 width=8) (actual time=0.355..8.359 rows=5462 loops=1)
Hash Cond: (film_actor.actor_id = actor.actor_id)
-> Seq Scan on film_actor (cost=0.00..84.62 rows=5462 width=4) (actual time=0.035..2.205 rows=5462 loops=1)
-> Hash (cost=4.00..4.00 rows=200 width=10) (actual time=0.303..0.303 rows=200 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 17kB
-> Seq Scan on actor (cost=0.00..4.00 rows=200 width=10) (actual time=0.027..0.143 rows=200 loops=1)
Planning time: 1.495 ms
Execution time: 15.426 ms
Nested Loop Left Join (cost=25.11..33.16 rows=1 width=51) (actual time=0.849..0.854 rows=1 loops=1)
-> Index Scan using idx_title on film (cost=0.28..8.29 rows=1 width=19) (actual time=0.045..0.048 rows=1 loops=1)
Index Cond: ((title)::text = 'ACADEMY DINOSAUR'::text)
-> Aggregate (cost=24.84..24.85 rows=1 width=32) (actual time=0.797..0.797 rows=1 loops=1)
-> Hash Join (cost=10.82..24.82 rows=5 width=6) (actual time=0.672..0.764 rows=10 loops=1)
Hash Cond: (film_actor.actor_id = actor.actor_id)
-> Bitmap Heap Scan on film_actor (cost=4.32..18.26 rows=5 width=2) (actual time=0.072..0.150 rows=10 loops=1)
Recheck Cond: (film_id = film.film_id)
Heap Blocks: exact=10
-> Bitmap Index Scan on idx_fk_film_id (cost=0.00..4.32 rows=5 width=0) (actual time=0.041..0.041 rows=10 loops=1)
Index Cond: (film_id = film.film_id)
-> Hash (cost=4.00..4.00 rows=200 width=10) (actual time=0.561..0.561 rows=200 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 17kB
-> Seq Scan on actor (cost=0.00..4.00 rows=200 width=10) (actual time=0.039..0.275 rows=200 loops=1)
Planning time: 1.722 ms
Execution time: 1.087 ms
ทำไมนี้ ฉันต้องการเรียนรู้ที่จะให้เหตุผลเกี่ยวกับสิ่งนี้ดังนั้นฉันสามารถเข้าใจสิ่งที่เกิดขึ้นและสามารถทำนายได้ว่าแบบสอบถามจะทำงานอย่างไรเมื่อขนาดของข้อมูลเพิ่มขึ้นและการตัดสินใจใดที่นักวางแผนจะทำภายใต้เงื่อนไขบางประการ
ความคิดของฉัน: ในLEFT JOIN
แบบสอบถามแรกดูเหมือนว่าแบบสอบถามย่อยจะถูกดำเนินการสำหรับภาพยนตร์ทุกเรื่องในฐานข้อมูลโดยไม่คำนึงถึงการกรองในแบบสอบถามภายนอกที่เราสนใจเพียงหนึ่งเรื่อง เหตุใดผู้วางแผนไม่สามารถมีความรู้นั้นในแบบสอบถามย่อยได้?
ในLEFT JOIN LATERAL
แบบสอบถามเราจะ 'ดัน' ที่กรองลงมากขึ้นหรือน้อยลง ดังนั้นปัญหาที่เรามีในข้อความค้นหาแรกจึงไม่ปรากฏที่นี่ดังนั้นประสิทธิภาพที่ดีขึ้น
ฉันเดาว่าฉันกำลังมองหากฎของหัวแม่มือ, ภูมิปัญญาทั่วไป, ... ดังนั้นเวทย์มนตร์นักวางแผนนี้จึงกลายเป็นธรรมชาติที่สอง - ถ้ามันเหมาะสม
อัปเดต (1)
การเขียนใหม่LEFT JOIN
ดังต่อไปนี้ยังให้ประสิทธิภาพที่ดีขึ้น (ดีกว่าเล็กน้อยLEFT JOIN LATERAL
):
select film.film_id, film.title, array_agg(a.first_name) as actors
from film
left join
(
select film_actor.film_id, actor.first_name
from actor
inner join film_actor using(actor_id)
) as a
on a.film_id = film.film_id
where film.title = 'ACADEMY DINOSAUR'
group by film.film_id
order by film.title;
GroupAggregate (cost=29.44..29.49 rows=1 width=51) (actual time=0.470..0.471 rows=1 loops=1)
Group Key: film.film_id
-> Sort (cost=29.44..29.45 rows=5 width=25) (actual time=0.428..0.430 rows=10 loops=1)
Sort Key: film.film_id
Sort Method: quicksort Memory: 25kB
-> Nested Loop Left Join (cost=4.74..29.38 rows=5 width=25) (actual time=0.149..0.386 rows=10 loops=1)
-> Index Scan using idx_title on film (cost=0.28..8.29 rows=1 width=19) (actual time=0.056..0.057 rows=1 loops=1)
Index Cond: ((title)::text = 'ACADEMY DINOSAUR'::text)
-> Nested Loop (cost=4.47..19.09 rows=200 width=8) (actual time=0.087..0.316 rows=10 loops=1)
-> Bitmap Heap Scan on film_actor (cost=4.32..18.26 rows=5 width=4) (actual time=0.052..0.089 rows=10 loops=1)
Recheck Cond: (film_id = film.film_id)
Heap Blocks: exact=10
-> Bitmap Index Scan on idx_fk_film_id (cost=0.00..4.32 rows=5 width=0) (actual time=0.035..0.035 rows=10 loops=1)
Index Cond: (film_id = film.film_id)
-> Index Scan using actor_pkey on actor (cost=0.14..0.17 rows=1 width=10) (actual time=0.011..0.011 rows=1 loops=10)
Index Cond: (actor_id = film_actor.actor_id)
Planning time: 1.833 ms
Execution time: 0.706 ms
เราจะให้เหตุผลเกี่ยวกับเรื่องนี้ได้อย่างไร
อัปเดต (2)
ฉันยังคงมีการทดลองบางอย่างและฉันคิดว่ากฎที่น่าสนใจของหัวแม่มือคือ: ใช้ฟังก์ชันการรวมสูง / ปลายที่เป็นไปได้ ข้อความค้นหาในการอัปเดต (1) อาจทำงานได้ดีขึ้นเนื่องจากเรากำลังรวบรวมในแบบสอบถามด้านนอกไม่อยู่ในแบบสอบถามภายใน
ดูเหมือนว่าจะมีผลบังคับใช้ถ้าเราเขียนLEFT JOIN LATERAL
ข้างต้นดังต่อไปนี้:
select film.film_id, film.title, array_agg(a.first_name) as actors
from film
left join lateral
(
select actor.first_name
from actor
inner join film_actor using(actor_id)
where film_actor.film_id = film.film_id
) as a
on true
where film.title = 'ACADEMY DINOSAUR'
group by film.film_id
order by film.title;
GroupAggregate (cost=29.44..29.49 rows=1 width=51) (actual time=0.088..0.088 rows=1 loops=1)
Group Key: film.film_id
-> Sort (cost=29.44..29.45 rows=5 width=25) (actual time=0.076..0.077 rows=10 loops=1)
Sort Key: film.film_id
Sort Method: quicksort Memory: 25kB
-> Nested Loop Left Join (cost=4.74..29.38 rows=5 width=25) (actual time=0.031..0.066 rows=10 loops=1)
-> Index Scan using idx_title on film (cost=0.28..8.29 rows=1 width=19) (actual time=0.010..0.010 rows=1 loops=1)
Index Cond: ((title)::text = 'ACADEMY DINOSAUR'::text)
-> Nested Loop (cost=4.47..19.09 rows=200 width=8) (actual time=0.019..0.052 rows=10 loops=1)
-> Bitmap Heap Scan on film_actor (cost=4.32..18.26 rows=5 width=4) (actual time=0.013..0.024 rows=10 loops=1)
Recheck Cond: (film_id = film.film_id)
Heap Blocks: exact=10
-> Bitmap Index Scan on idx_fk_film_id (cost=0.00..4.32 rows=5 width=0) (actual time=0.007..0.007 rows=10 loops=1)
Index Cond: (film_id = film.film_id)
-> Index Scan using actor_pkey on actor (cost=0.14..0.17 rows=1 width=10) (actual time=0.002..0.002 rows=1 loops=10)
Index Cond: (actor_id = film_actor.actor_id)
Planning time: 0.440 ms
Execution time: 0.136 ms
ที่นี่เราขยับarray_agg()
ขึ้นไป LEFT JOIN LATERAL
ที่คุณสามารถดูแผนนี้ยังเป็นที่ดีขึ้นกว่าเดิม
ที่กล่าวว่าฉันไม่แน่ใจว่ากฎง่ายๆนี้ ( ใช้ฟังก์ชั่นรวมสูง / ช้าที่สุดเท่าที่เป็นไปได้ ) เป็นจริงในกรณีอื่น ๆ
ข้อมูลเพิ่มเติม
ซอ: https://dbfiddle.uk/?rdbms=postgres_10&fiddle=4ec4f2fffd969d9e4b949bb2ca765ffb
รุ่น: PostgreSQL 10.4 บน x86_64-pc-linux-musl รวบรวมโดย gcc (อัลไพน์ 6.4.0) 6.4.0, 64-bit
สภาพแวดล้อม: นักเทียบท่า: docker run -e POSTGRES_PASSWORD=sakila -p 5432:5432 -d frantiseks/postgres-sakila
. โปรดทราบว่าภาพบนฮับ Docker นั้นล้าสมัยดังนั้นฉันจึงสร้างบิลด์ก่อน: build -t frantiseks/postgres-sakila
หลังจากทำการโคลนที่เก็บ git
คำจำกัดความของตาราง:
ฟิล์ม
film_id | integer | not null default nextval('film_film_id_seq'::regclass)
title | character varying(255) | not null
Indexes:
"film_pkey" PRIMARY KEY, btree (film_id)
"idx_title" btree (title)
Referenced by:
TABLE "film_actor" CONSTRAINT "film_actor_film_id_fkey" FOREIGN KEY (film_id) REFERENCES film(film_id) ON UPDATE CASCADE ON DELETE RESTRICT
นักแสดงชาย
actor_id | integer | not null default nextval('actor_actor_id_seq'::regclass)
first_name | character varying(45) | not null
Indexes:
"actor_pkey" PRIMARY KEY, btree (actor_id)
Referenced by:
TABLE "film_actor" CONSTRAINT "film_actor_actor_id_fkey" FOREIGN KEY (actor_id) REFERENCES actor(actor_id) ON UPDATE CASCADE ON DELETE RESTRICT
film_actor
actor_id | smallint | not null
film_id | smallint | not null
Indexes:
"film_actor_pkey" PRIMARY KEY, btree (actor_id, film_id)
"idx_fk_film_id" btree (film_id)
Foreign-key constraints:
"film_actor_actor_id_fkey" FOREIGN KEY (actor_id) REFERENCES actor(actor_id) ON UPDATE CASCADE ON DELETE RESTRICT
"film_actor_film_id_fkey" FOREIGN KEY (film_id) REFERENCES film(film_id) ON UPDATE CASCADE ON DELETE RESTRICT
ข้อมูล: นี่คือจากฐานข้อมูลตัวอย่างของ Sakila คำถามนี้ไม่ใช่กรณีจริงฉันใช้ฐานข้อมูลนี้เป็นฐานข้อมูลตัวอย่างการเรียนรู้เป็นส่วนใหญ่ ฉันได้รับการแนะนำให้รู้จักกับ SQL เมื่อหลายเดือนก่อนและฉันพยายามที่จะขยายความรู้ของฉัน มันมีการแจกแจงต่อไปนี้:
select count(*) from film: 1000
select count(*) from actor: 200
select avg(a) from (select film_id, count(actor_id) a from film_actor group by film_id) a: 5.47