【问题标题】:Explanation needed for missing rows with left join and count()左连接和 count() 缺失行需要解释
【发布时间】:2011-09-08 06:02:40
【问题描述】:

当我将 WHERE 子句添加到具有 COUNT(*) 的 LEFT JOIN 的查询时,有人可以帮我理解以下行为吗?

我有两张桌子:

TABLE 1: customers
customer_id | name
------------------
1 | Bob
2 | James
3 | Fred

TABLE 2: orders
order_id | customer_id | order_timestamp
----------------------------------------
1000 | 1 | 2011-01-01 00:00
1001 | 1 | 2011-01-05 00:00
1002 | 2 | 2011-01-10 00:00

现在下面的查询告诉我每个客户下了多少订单:

select c.customer_id, count(o.order_id)
from customers c
left join orders o using (customer_id)
group by 1

customer_id | count
-------------------
1 | 2
2 | 1
3 | 0

这很好用,但是如果我在查询中添加 WHERE 子句,即使我正在执行 LEFT JOIN,查询也不再为未下任何订单的客户输出零计数:

select c.customer_id, count(o.order_id)
from customers c
left join orders o using (customer_id)
where o.order_timestamp >= '2011-01-05'
group by 1

customer_id | count
-------------------
1 | 1
2 | 1

现在,如果我将 WHERE 条件作为 LEFT JOIN 的一部分移动,如下所示,对于未下订单的客户,我将获得零计数:

select c.customer_id, count(o.order_id)
from customers c
left join orders o on (c.customer_id = o.customer_id) and (o.order_timestamp >= '2011-01-05')
group by 1

我很困惑为什么第二个查询不起作用,但第三个却起作用?有人可以给我一个解释吗?也不确定这是否重要,但我正在使用 postgres。谢谢!

【问题讨论】:

    标签: sql postgresql aggregate-functions


    【解决方案1】:

    Chirs 是对的,null 不大于或等于任何值。因此,当您在 where 子句中包含您的条件时,它适用于左连接生成的结果的最终视图(表), 在此结果中,您的条件将删除时间戳为空的行。

    但是,当您在执行连接期间应用相同条件时,条件仅适用于订单表,而不是执行左连接。所以它不会删除时间戳为空的行。

    因此,在生成最终表之前应用的第三个查询条件和生成最终表之后应用的第二个查询条件

    【讨论】:

      【解决方案2】:

      在处理外连接(右、左)时,过滤条件的放置很重要。 OUTER JOIN 的 ON 子句中的条件在 JOIN 之前应用; WHERE 子句中的条件在 JOIN 之后应用 -- 应用于使用 JOIN 的结果集。

         SELECT c.customer_id, 
                COUNT(o.order_id)
           FROM CUSTOMERS c
      LEFT JOIN ORDERS o ON o.customer_id - c.customer_id
                        AND o.order_timestamp >= '2011-01-05'
       GROUP BY c.customer_id
      

      序数

      序数,意思是使用一个数值来引用 SELECT 子句中列的数字位置,这不是推荐的做法。如果有人更改查询(比如添加一列),可能会严重影响您的查询。

      【讨论】:

      • 对我来说,我错过了GROUP BY。谢谢。
      【解决方案3】:

      这是因为 NULL 不大于或等于任何值;如果您将 WHERE 子句更改为 where o.order_timestamp is null or o.order_timestamp >= '2011-01-05',那么您将获得与 join 子句限制相同的行为。

      但请注意 - 我会推荐 join 子句方法,因为它更接近您想要做的事情。此外,我上面提到的对 WHERE 子句的更改只有在 order_timestamp 列不可为空时才有效——如果是,那么您应该使用不同的列进行空检查(例如,where o.primarykey is null or o.order_timestamp >= '2011-01-05')。

      【讨论】:

      • 谢谢 - 确实有效!但是我仍然很困惑。如果 WHERE 子句失败,是否不应该加入该特定 customer_id 的零行,这意味着 count(*) 将为零?
      • 任何时候 where 子句对特定行返回 false,该行将从结果集中排除;这发生在最终结果集上,而不是中间结果集上(当您将该子句应用于您的连接时,它会按照您的建议进行操作)。
      • 感谢您的解释!
      • 您展示的 WHERE 子句和 JOIN 子句之间的区别在于,当您在左连接子句上添加额外的约束时,您并没有改变连接左侧的表的事实仍然返回,它只是没有匹配到右侧表中的行。当您使用 where 子句时,连接已经发生,现在您正在选择要返回的连接行。
      • 将约束添加到 WHERE 子句的一个主要含义是查询必须考虑所有 o.order_timestamp 值以确定一个是否为空。至少在我的情况下,这有效地破坏了我的 300 万行表中的索引。将约束添加到 JOIN 不会对性能产生负面影响。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-04-18
      • 2015-03-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-02
      相关资源
      最近更新 更多