PostgreSQL如何处理1 + n查询?

时间:2018-05-17 08:10:12

标签: sql postgresql

我正在测试Sakila数据库,请参阅http://www.postgresqltutorial.com/postgresql-sample-database/。该数据库有三种关系:

  • 电影:film_id,title
  • 演员:actor_id,first_name
  • film_actor :film_id,actor_id

我想列出所有电影和每部电影,我想列出所有演员在那部电影中播放。我以下面的查询结束:

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 查询。

2 个答案:

答案 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;

使用左外连接,即使电影没有演员,也可以获得结果。