MySQL 使用 LEFT JOIN 的查询不返回空结果

MySQL query with LEFT JOIN not returning empty results

我创建了一个日历 table,里面只有很多日期。我的活动 table 然后有排列的日期,如果一天没有活动,我想 return 为零。我有以下内容:

SELECT cDate, Branch, IFNULL(COUNT(*),0) as count
FROM Events E LEFT JOIN Calendar C ON C.cDate = DATE(E.eventDate)
WHERE cDate BETWEEN '2018-04-14' AND '2018-04-18'
GROUP BY Branch, cDate
ORDER BY cDate

但是当前显示的结果:

  cDate    | Branch | count
2018-04-14 |   1    |  5
2018-04-14 |   2    |  4
2018-04-16 |   1    |  1
2018-04-16 |   2    |  3
2018-04-17 |   1    |  5
2018-04-18 |   1    |  4

但是我打算让它显示任何计数为零的日期,如下所示:

  cDate    | Branch | count
2018-04-14 |   1    |  5
2018-04-14 |   2    |  4
2018-04-15 |   1    |  0
2018-04-15 |   2    |  0
2018-04-16 |   1    |  1
2018-04-16 |   2    |  3
2018-04-17 |   1    |  5
2018-04-17 |   2    |  0
2018-04-18 |   1    |  4
2018-04-18 |   2    |  0

有 2 个问题:

  1. 日历 table 有完整的日期列表,因此它应该在左联接的左侧。

  2. 您不仅需要完整的日期列表,还需要完整的日期-分支组合列表。

我假设您有一个分支 table 来存储完整的分支列表。我在日历 table 上交叉加入这个,然后在实际事件中加入结果 table:

select c.cdate, b.branch, count(e.eventdate)
from (branches b join calendar c)
left join events e on b.branch=e.branch and c.cdate=date(e.eventdate)
group by c.cdate, b.branch

WHERE 子句中要求外部连接的列 table 有效地为非 NULL 的任何条件 "negates" 连接的外部性,渲染它相当于一个内连接。

这个条件

   cdate BETWEEN '2018-04-14' AND '2018-04-18'

只有具有非 NULL 值 cdate 的行才会满足。

这样思考左外连接操作对(我)有帮助:

当左侧的一行没有右侧的匹配行时,在右侧发明了一个虚拟行作为匹配行。 (连接需要匹配的行,因此该行可以被 returned。)generated/invented 虚拟行完全由 NULL 个值组成。

因此,对于您所观察到的行为的部分修复是将该条件从 WHERE 子句重新定位到外部联接的 ON 子句中。

该更改可能是解决问题所需的全部内容,但是...我不愿特别推荐将其作为解决方案,因为我不了解实际规范。


另一个建议:

作为对未来读者的帮助,请考虑限定所有专栏引用。 (我们注意到 SQL 语句已经为 table 分配了别名。)

根据问题中发布的信息,我们无法确定 branch 列来自哪个 table。看起来 Calendar 可能只是一个唯一日期列表,因此我们假设 branch 列位于 Event table.


我怀疑所需的结果会 return 像这样的查询:

SELECT c.cdate
     , b.branch
     , COUNT(e.branch)  AS `count`
  FROM Calendar c
 CROSS
  JOIN Branch b 

  LEFT
  JOIN Events e
    ON e.eventdate  >= c.cdate
   AND e.eventdate   < c.cdate + INTERVAL 1 DAY
   AND e.branch      = b.branch

 WHERE c.cdate BETWEEN '2018-04-14' AND '2018-04-18'
 GROUP
    BY c.cdate
     , b.branch
 ORDER
    BY c.cdate
     , b.branch

让我们稍微解压一下。

我们正在从 Calendar 获取指定范围内的所有日期。 (我们 suspect/assume cdate 是 DATE 数据类型,并且保证是唯一的。在这个查询中,我们基本上使用 Calendar 来生成一组连续的日期值。)

并且我们想要从 Calendar 中获取与每个特定日期相关的 Events 数量的 "count"。

请注意 COUNT() 聚合将 return 一个非 NULL 值;如果我们对计算结果为 NULL 的表达式进行计数,则计数不会递增。我们不需要将 COUNT() 聚合包装在 IFNULL/COALESCE/CASE 中以用零替换 NULL..

我们正在做 "left join"。这意味着我们希望驾驶table(在这种情况下为Calendar)在左侧,我们希望table从 右侧 找到匹配项。如果在右侧找到 not 匹配行,则由所有 NULL 值组成的虚拟行将是 "generated",因此可以 returned 连接行.

因为我们想通过“cdate”和“branch”获得计数,我们还需要“branch”值的行源。 (正如@Shadow 指出的那样,我们可以使用 table 代替内联视图 b。内联视图 b 的目的是获取 branch 的不同列表我们想要的值 returned.)

CROSS JOIN 会给我们一个叉积。也就是说,所有 cdate 值都与所有 branch 值匹配,所以我们有一个完整的集合。五个 cdate 值,两个 branch 值,得到一组 10 行,我们想要的行 return。当给定 cdatebranch.[=63 没有匹配的 Event 行时,我们需要这些行能够 return "zero" 的计数=]

同样,我们假设 cdateCalendar 中是唯一的,因此我们 return 从 Calendar 开始(最多)五行。我们还假设需要检查 Event 中的(可能)更多行,以将 eventdatecdate 进行比较。我们不想阻止 MySQL 在 eventdate 列上有效使用索引范围操作(有可用的 suitable 索引),所以我们避免包装 eventdate 函数中的列并改为引用裸列。


我们只是猜测需求,所以我的建议可能不符合实际规格。


跟进

我们需要 branch 值的行源。这可以是 table 或内联视图查询。原始 SQL 没有假定 Branch table,因此我们使用查询来获取不同的分支列表:

  JOIN  ( SELECT br.branch
           FROM Events br
          GROUP BY br.branch
       ) b

我原始答案中的内联视图查询与修改后的查询中的 Branch table 具有相同的目的。它 return 是 Events table 中出现的 branch 值的不同列表。如果以 branch 作为前导列的索引可用,MySQL 可以使用该索引。

最大的区别在于 branch 值(例如 3)出现在 Branch table 中,但没有出现在 Event table。使用 Event 的内联视图,我们不会 return branch = 3 的任何行。

我会通过使用交叉连接来解决这个问题 link 日历中所需的日期。然后将其与 Event table 结合以获取 eventDate.

的计数
SELECT c.cDate, b.Branch, COUNT(e.EventDate) as count
FROM
(SELECT *
FROM Calendar C WHERE
cDate BETWEEN '2018-04-14' AND '2018-04-18' ) c
CROSS JOIN 
(SELECT distinct branch from Events ) b
LEFT JOIN
events e
ON c.cDate = DATE(e.EventDate) AND e.branch = b.branch
GROUP BY c.cDate, b.Branch
ORDER BY c.cDate, b.Branch