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

时间:2018-04-17 23:48:32

标签: mysql outer-join

我创建了一个日历表,其中只包含大量日期。然后我的事件表有排队的日期,如果一天没有事件,我想为此返回零。我有以下内容:

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

3 个答案:

答案 0 :(得分:1)

WHERE子句中要求外连接表中的列有效为非NULL的任何条件“否定”连接的外部,使其等效于内部连接。 / p>

这个条件

   cdate BETWEEN '2018-04-14' AND '2018-04-18'
只有非{NULL}值为cdate的行才能满足

以这种方式帮助(我)考虑左外连接操作:

当左侧的行没有来自右侧的匹配行时,在右侧发明虚拟行以用作匹配行。 (连接需要匹配行,因此可以返回行。)生成/发明的虚拟行完全由NULL值组成。

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

这种改变可能是解决问题所需的全部,但是......我特别建议将其作为一种解决方案,因为我对实际规范并不了解。

另一个建议:

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

根据问题中发布的信息,我们无法确定branch列所属的表格。看起来Calendar可能只是一个唯一日期列表,因此我们假设在branch表中找到了Event列。

我怀疑这样的查询会返回所需的结果:

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获取指定范围内的所有日期。 (我们怀疑/假设cdate是DATE数据类型,并且保证是唯一的。在此查询中,我们基本上使用Calendar生成一组连续的日期值。)

我们希望从Events获得与{1}}相关的每个特定日期Calendar的数量。

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

我们正在进行“左连接”。这意味着我们希望驱动表(在这种情况下为Calendar)位于 left 侧,我们希望我们找到匹配的表位于右侧方面。如果在右侧找到匹配的行 not ,则将“生成”包含所有NULL值的虚拟行,因此可以返回连接的行。

由于我们希望按“cdate”和“branch”获取计数,因此我们还需要“branch”值的行来源。 (正如@Shadow所说,我们可以使用表格代替内联视图b。内联视图b的目的是获取我们想要返回的branch值的明确列表。)

CROSS JOIN将为我们提供跨产品。也就是说,所有cdate值都与所有branch值匹配,因此我们有一套完整的值。五个cdate值,两个branch值,为我们提供了一组10行,即我们想要返回的行。当给定的Eventcdate没有匹配的branch行时,我们需要这些行能够返回“零”。

同样,我们假设cdate中的Calendar是唯一的,因此我们会从Calendar返回(最多)五行。我们还假设将(可能)需要检查来自Event的更多行,以便将eventdatecdate进行比较。我们不希望阻止MySQL在eventdate列上有效地使用索引范围操作(使用合适的索引),因此我们避免在函数中包装eventdate列并引用裸而是专栏。

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

后续

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

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

原始答案中的内联视图查询与修订查询中的Branch表的用途相同。它返回branch表中出现的Events值的不同列表。如果具有branch作为前导列的索引可用,则MySQL可以使用该索引。

最大的区别在于branch表中显示的Branch值(例如3),但未显示在Event表中。使用Event的内嵌视图,我们不会返回branch = 3的任何行。

答案 1 :(得分:0)

有两个问题:

  1. 日历表包含完整的日期列表,因此它应位于左连接的左侧。

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

  3. 我假设您有一个分支表来存储完整的分支列表。我在日历表上交叉连接,然后将结果连接到实际事件表:

    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
    

答案 2 :(得分:0)

我会通过使用交叉联接来链接日历中的所需日期。然后将其与Event表连接以获取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