左连接不返回空结果的mysql查询

bt1cpqcv  于 2021-06-25  发布在  Mysql
关注(0)|答案(3)|浏览(348)

我创建了一个日历表,其中只包含大量的日期。然后,我的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
vpfxa7rd

vpfxa7rd1#

中的任何条件 WHERE 要求外部联接表中的列为非空的子句有效地“否定”联接的外部性,使其等效于内部联接。
这种情况

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

将仅由具有非空值的行满足 cdate .
它有助于(我)这样思考左外连接操作:
当左侧的行没有右侧的匹配行时,在右侧创建一个虚拟行作为匹配行(连接需要匹配的行,以便可以返回该行。)生成的/虚构的虚拟行完全由 NULL 价值观。
因此,对你所观察到的行为的一个部分修正就是将该条件从 WHERE 条款进入 ON 外连接的子句。
这种改变也许是解决问题所需要的全部,但是。。。我不太愿意具体推荐这个解决方案,因为我不了解实际的规范。
另一项建议:
为了帮助将来的读者,请考虑限定所有列引用(我们注意到sql语句已经为表分配了别名。)
从问题中公布的信息来看,我们无法确定是哪个表 branch 列来自。看起来像 Calendar 可能只是一个独特日期的列表,所以我们假设 branch 列位于 Event table。
我怀疑这样的查询会返回所需的结果:

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 是日期数据类型,并且保证是唯一的。在这个查询中,我们基本上使用 Calendar 生成一组连续的日期值。)
我们想知道 Events 与每个特定日期相关 Calendar .
请注意 COUNT() aggregate将返回一个非空值;如果我们对计算结果为null的表达式进行计数,则计数不会递增。我们不需要把衣服包起来 COUNT() 在ifnull/coalesce/case中聚合以将null替换为零。。
我们在做“左连接”。那意味着我们想要驾驶台( Calendar 在本例中)位于左侧,我们希望从中查找匹配项的表位于右侧。如果右侧没有找到匹配的行,则会“生成”一个包含所有空值的虚拟行,这样就可以返回一个联接行。
因为我们想通过 cdate “和” branch ,我们还需要 branch “价值观(正如@shadow所指出的,我们可以使用一个表来代替内联视图 b . 内联视图的用途 b 就是得到一个不同的 branch 我们希望返回的值。)
这个 CROSS JOIN 会得到一个交叉积。那就是,全部 cdate 与所有匹配的值 branch 值,所以我们有一个完整的集合。5 cdate 值,2 branch 值,得到一组10行,我们要返回的行。我们需要这些行能够在没有匹配的情况下返回“零”计数 Event 指定行的行数 cdate 以及 branch .
再说一次,我们假设 cdate 是独一无二的 Calendar ,因此我们最多从 Calendar . 我们还假设从 Event 将需要检查,比较 eventdatecdate . 我们不想阻止mysql有效地使用 eventdate 列(有合适的索引可用),因此我们避免 Package eventdate 列,并改为引用裸列。
我们只是猜测要求,所以我的建议可能不符合实际规格。
后续行动
我们需要一个资料来源 branch 价值观。可以是表,也可以是内联视图查询。原始sql没有假设 Branch 表,因此我们使用查询来获得分支的不同列表:

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

我的原始答案中的内联视图查询与 Branch 修改后的查询中的表。它返回一个不同的 branch 出现在 Events table。如果索引 branch 由于前导列可用,mysql可以使用索引。
最大的不同是 branch 出现在 Branch 表,但不显示在 Event table。具有的内联视图 Event ,我们不会为返回任何行 branch = 3.

lsmd5eda

lsmd5eda2#

有两个问题:
日历表有完整的日期列表,因此它应该位于左侧连接的左侧。
你不仅需要完整的日期列表,还需要完整的日期-分支组合列表。
我假设您有一个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
0g0grzrc

0g0grzrc3#

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

相关问题