我正在测试Sakila数据库,请参阅http://www.postgresqltutorial.com/postgresql-sample-database/。该数据库有三种关系:
我想列出所有电影和每部电影,我想列出所有演员在那部电影中播放。我以下面的查询结束:
select film_id, title, array
(
select first_name
from actor
inner join film_actor
on actor.actor_id = film_actor.actor_id
where film_actor.film_id = film.film_id
) as actors
from film
order by title;
从概念上讲,这是 1 + n查询:
one query: get films
n queries: for each film f
f.actors = array(get actors playing in f)
我一直都知道应该不惜一切代价避免 1 + n个查询,因为这不能很好地扩展。
所以这让我想知道:PostgreSQL如何在内部实现它?我们假设我们有1000部电影,它是否在内部执行1000 select actor.first_name from actor inner join ...
次查询?或者PostgreSQL对此更聪明,并且做了类似下面的事情吗?
1. one query: get films
2. one query: get actors related to these films while keeping reference to film_id
3. internally: for each film f
f.actors = array(subset of (2) according to film_id)
这是 1 + 1 查询。
答案 0 :(得分:1)
这可能更适合评论,但时间太长了。
虽然我遵循查询的逻辑,但我更喜欢将其表达为:
select f.film_id, f.title,
(select array_agg(a.first_name)
from actor a inner join
film_actor fa
on a.actor_id = fa.actor_id
where fa.film_id = f.film_id
) as actors
from film f
order by f.title;
显式array_agg()
澄清了逻辑。您正在聚合子查询,将结果作为一个数组组合在一起,然后将其作为外部查询中的列包含在内。
答案 1 :(得分:1)
您正在考虑嵌套循环。在使用关系数据库时,您应该克服这一点(除非您使用的是MySQL)。
您所描述的“1 + n”是一个嵌套循环:您扫描一个表,对于找到的每一行,您扫描另一个表。
编写SQL查询的方式,PostgreSQL别无选择,只能执行嵌套循环。
只要外表(示例中为film
)的行数很少,这就很好。一旦外桌变大,性能就会迅速恶化。
除嵌套循环外,PostgreSQL还有另外两种连接策略:
散列连接:扫描内部关系并创建散列结构,其中散列键是连接键。然后扫描外部关系,并为找到的每一行探测哈希值。
将其视为一种散列连接,但在内部,您可以拥有高效的内存数据结构。
合并连接:两个表都在连接键上排序,并通过同时扫描结果进行合并。
建议您在没有“相关子查询”的情况下编写查询,以便PostgreSQL可以选择最佳连接策略:
SELECT film_id, f.title, array_agg(a.first_name)
FROM film f
LEFT JOIN film_actor fa USING (film_id)
LEFT JOIN actor a USING (actor_id)
GROUP BY f.title
ORDER BY f.title;
使用左外连接,即使电影没有演员,也可以获得结果。