具有左连接的MySQL查询不返回空结果

具有左连接的MySQL查询不返回空结果,mysql,outer-join,Mysql,Outer Join,我创建了一个日历表,其中只包含大量日期。然后,我的events表有一系列的日期,如果一天没有任何事件,我想返回一个零。我有以下资料: 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,

我创建了一个日历表,其中只包含大量日期。然后,我的events表有一系列的日期,如果一天没有任何事件,我想返回一个零。我有以下资料:

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
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
有两个问题:

  • 日历表包含完整的日期列表,因此它应该位于左侧联接的左侧

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

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

    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
    子句中要求外部联接表中的列为非空的任何条件都会有效地“否定”联接的外部性,使其等效于内部联接

    这种情况

       cdate BETWEEN '2018-04-14' AND '2018-04-18'
    
    只有非空值为
    cdate
    的行才能满足要求

    这有助于(我)以这种方式思考左外联接操作:

    当左侧的行没有右侧的匹配行时,在右侧创建一个虚拟行作为匹配行。(连接需要匹配的行,以便可以返回该行。)生成/创建的虚拟行完全由
    NULL
    值组成

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

    这种改变可能是解决问题所需要的全部,但是。。。我不太愿意特别推荐它作为解决方案,因为我不了解实际的规范


    另一项建议:

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

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


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

      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
    
    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
    
    原始答案中的内联视图查询与修订后的查询中的
    Branch
    表具有相同的用途。它返回
    事件
    表中出现的
    分支
    值的不同列表。如果可以使用以
    branch
    作为前导列的索引,MySQL可以使用该索引


    最大的区别在于出现在
    分支
    表中但不出现在
    事件
    表中的
    分支
    值(例如3)。使用
    事件的内联视图
    ,我们不会为
    分支
    =3返回任何行

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

    如果我误解了这一点,请向我坦白。我有一个Branchs表,但是Branchs表中没有需要为所需结果调用的数据,那么为什么我需要加入branch表呢?该表只有分支ID和分支名称。因为您希望按日期和分支获取计数,包括那些没有任何事件关联的日期和分支组合。好的,我不确定为什么需要将其与分支表对齐,尽管我的主要结果是每个日期都有一行。难道我不能返回一个空结果,我可以告诉它被归为零吗?它确实有效,但是添加第二个where子句
    和EventType=2
    现在会删除所有的零结果。EventType没有表,它只是正在发生的事件类型的简单代码。。。我没有将此添加到原始问题中,因为我假设它只会过滤结果。您需要将该过滤条件移动到
    on
    子句中。如果您有任何后续问题,请单独提问。OP确实有一个Branchs表,请参见我回答下面的注释。@shadow:OP可以使用
    branch
    表作为行源,而不是我示例中的内联视图。(重要的一点是,我们需要一个
    branch
    值的列表和一个
    cdate
    值的列表,并生成这些值的笛卡尔/半笛卡尔/叉积,然后对事件进行连接以获得相关事件的计数。如果我们想要获得“零”计数和计数()聚合需要对一个表达式进行操作,当没有匹配的事件行时,该表达式的计算结果将为NULL。是的,我知道。我在回答中写下了所有这些:)顺便说一句,OP应该使用分支表,而不是查询事件表中的分支列表。可能有一些分支没有任何与之相关的事件,加上分支表的大小可能比事件表小。@spencer7593感谢您的深入解释,我明天将完整地讨论它(这里只需点击凌晨2点),然而,我刚刚调整并测试了它,它完全符合我的需要,结果完全正确。谢谢。@BN83正如我在两个答案中已经指出的那样:查询事件表中的分支列表效率低下,如果您的分支没有任何与之关联的事件,那么该分支将不会显示在报告中。你应该使用现有的分支表,而不是斯宾塞建议的解决方案。答案指出你的问题缺少重要(但基本)信息。他们不应该用猜测来回答,他们应该为您提供inf而发表评论