查询中的查询:有更好的方法吗?

时间:2011-01-12 04:31:13

标签: sql postgresql join query-optimization subquery

随着我构建更大,更高级的Web应用程序,我发现自己编写了极其冗长而复杂的查询。我倾向于在查询中编写很多查询,因为我觉得从PHP调用数据库比制作数据并关联数据更好。

然而,任何对SQL有所了解的人都知道JOIN。就个人而言,我之前使用了JOIN或者两个,但是当我发现使用子查询时很快停止了,因为我觉得编写和维护起来更容易,更快。

通常,我会做一些子查询,这些子查询可能包含来自相对表的一个或多个子查询 考虑这个例子:

SELECT 
  (SELECT username FROM users WHERE records.user_id = user_id) AS username,
  (SELECT last_name||', '||first_name FROM users WHERE records.user_id = user_id) AS name,
  in_timestamp,
  out_timestamp
FROM records
ORDER BY in_timestamp

很少,我会在WHERE条款之后进行子查询 考虑这个例子:

SELECT
  user_id,
  (SELECT name FROM organizations WHERE (SELECT organization FROM locations WHERE records.location = location_id) = organization_id) AS organization_name
FROM records
ORDER BY in_timestamp

在这两种情况下,如果我决定使用JOIN重写查询,我会看到任何改进吗?

作为一个笼统的问题,使用子查询或JOIN有哪些优点/缺点?一种方式比另一方更正确或被接受吗?

4 个答案:

答案 0 :(得分:2)

在简单的情况下,查询优化器应该能够为简单连接而不是简单的子选择生成相同的计划。

但总的来说(在适当情况下),你应该赞成连接而不是子选择。

另外,您应该避免使用相关子查询(内部表达式引用外部的查询),因为它们实际上是for循环中的for循环)。在大多数情况下,相关子查询可以写为连接。

答案 1 :(得分:1)

JOIN比分开[sub]查询更可取 如果子选择(AKA子查询)与外部查询不相关,则优化器很可能会扫描子选择中的表一次,因为该值不太可能更改。当您具有相关性时,就像在提供的示例中一样,单次通过优化的可能性变得非常小。在过去,人们一直认为相关的子查询执行RBAR - Row By Agonizing Row。通过JOIN,可以实现相同的结果,同时确保单次通过表格。

这是对所提供查询的正确重写:

   SELECT u.username,
          u.last_name||', '|| u.first_name AS name,
          r.in_timestamp,
          r.out_timestamp
     FROM RECORDS r 
LEFT JOIN USERS u ON u.user_id = r.user_id
 ORDER BY r.in_timestamp

...因为如果USERS表中不存在user_id,则子选择可以返回NULL。否则,您可以使用INNER JOIN:

  SELECT u.username,
         u.last_name ||', '|| u.first_name AS name,
         r.in_timestamp,
         r.out_timestamp
    FROM RECORDS r 
    JOIN USERS u ON u.user_id = r.user_id
ORDER BY r.in_timestamp

使用JOIN语法也可以派生表/内联视图。

答案 2 :(得分:1)

a)我首先指出两者不一定是可互换的。按照您的要求进行嵌套需要0或1个匹配值,否则您将收到错误。联接不提出此类要求,可能会排除记录或引入更多记录,具体取决于您的数据和联接类型。

b)在性能方面,您需要检查查询计划,但嵌套示例不太可能比表连接更有效。通常子查询每行执行一次,但这在很大程度上取决于您的数据库,唯一约束,foriegn键,而不是null等。也许DB可以更有效地重写但是连接可以使用更多种技术,从不同的驱动数据表等因为它们做了不同的事情(尽管根据您的数据,您可能没有观察到输出的任何差异)。

c)我认识的大多数数据库感知程序员会查看嵌套查询并使用连接重写,但数据应该是“干净”的。

d)关于“正确性” - 我希望在必要时(例如唯一的用户ID)对您的数据进行适当的约束备份。你作为一个人可能做出某些假设,但除非你告诉它,否则数据库引擎不能。它知道得越多,它(和你)就能做得更好。

答案 3 :(得分:0)

大多数情况下加入会更快。

让我们举一个例子。

让我们使用您的第一个查询:

SELECT 
(SELECT username FROM users WHERE records.user_id = user_id) AS username,
  (SELECT last_name||', '||first_name FROM users WHERE records.user_id = user_id) AS name,
  in_timestamp,
  out_timestamp
FROM records
ORDER BY in_timestamp

现在考虑我们在记录中有100条记录,在用户中有100条记录。(假设我们在user_id上没有索引)

因此,如果我们了解您的算法,它会说: 对于每条记录    扫描用户中的所有100条记录以查找用户名    扫描用户中的所有100条记录以查找姓氏和名字

所以它就像我们扫描用户表100 * 100 * 2一样。真的值得吗?如果我们考虑使用user_id的索引,它会让事情变得更好,但它仍然值得。

现在考虑一个连接(嵌套循环几乎会产生与上面相同的结果,但考虑一个散列连接): 就像是。 制作用户的哈希映射。 对于每条记录    在Hashmap中查找映射记录。这肯定比循环和找到记录要快得多。

很明显,连接应该是有利的。

注意:100记录使用的示例可能会产生相同的计划,但其目的是分析它如何影响性能。