【问题标题】:MySQL query with LEFT JOIN not returning empty results带有 LEFT JOIN 的 MySQL 查询不返回空结果
【发布时间】:2018-04-17 23:48:32
【问题描述】:

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

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

【问题讨论】:

  • 答案指出您的问题缺少重要(但基本)信息。他们不应该用猜测来回答,他们应该评论让你提供信息。请阅读minimal reproducible examplemeta.stackoverflow.com/q/333952/3404097并采取行动。
  • 嗨。始终在 Google 上搜索您的问题/问题/目标/desiderata 的许多清晰、简洁、特定的版本/变体/措辞,带和不带您的特定字符串/名称/代码,并从许多问题中阅读许多答案,这应该会通知您进一步的 Google 搜索。如果在应用所学知识并重复此操作后找不到答案,请提出问题。使用最常见的高效关键字作为标签。使用最佳搜索作为标题。例如,“不返回空结果”只是您意思的一部分;然而谷歌搜索你的标题仍然会立即给出相关的答案。

标签: mysql outer-join


【解决方案1】:

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

这个条件

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

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

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

当左侧的一行没有右侧的匹配行时,在右侧发明一个虚拟行作为匹配行。 (连接需要匹配的行,以便可以返回该行。)生成/发明的虚拟行完全由 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 来生成一组连续的日期值。)

我们希望从Calendar 获得与每个特定日期相关的Events 数量的“计数”。

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

我们正在执行“左连接”。这意味着我们希望驾驶桌(在本例中为Calendar)位于左侧,并且我们希望我们从中查找匹配的表格位于右侧 em>边。如果在右侧找到匹配行,则会“生成”一个由所有 NULL 值组成的虚拟行,因此可以返回一个连接行。

由于我们想通过“cdate”和“branch”获取计数,我们还需要“branch”值的行源。 (正如@Shadow 所指出的,我们可以使用表格代替内联视图b。内联视图b 的目的是获取我们想要返回的branch 值的不同列表。)

CROSS JOIN 将为我们提供交叉产品。也就是说,所有cdate 值与所有branch 值匹配,所以我们有一个完整的集合。五个cdate 值,两个branch 值,得到一组10 行,我们想要返回的行。当给定的 cdatebranch 没有匹配的 Event 行时,我们需要这些行能够返回“零”计数。

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


我们只是在猜测需求,所以我的建议可能无法满足实际规范。


跟进

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

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

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

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

【讨论】:

  • OP 确实有一个分支表,请参阅我的答案下方的 cmets。
  • @shadow: OP 可以使用branch 表作为行源,代替我示例中的内联视图。 (重要的一点是我们需要一个branch 值列表和一个cdate 值列表,并生成它们的笛卡尔/半笛卡尔/叉积,然后加入事件以获取计数相关事件。如果我们想要获得“零”计数,这需要是一个外连接,并且 COUNT() 聚合需要对一个表达式进行操作,当没有匹配的事件行时,该表达式将评估为 NULL。
  • 是的,我知道。我在我的回答中写下了所有这些:) 顺便说一句,OP 应该使用分支表,而不是查询事件表以获取分支列表。可能有没有任何事件关联的分支,而且分支表可能比事件表小。
  • @spencer7593 感谢深入的解释,我将在明天完整地完成它(这里只是凌晨 2 点),但是我刚刚对其进行了调整和测试,它完全符合我的需要正确的结果。谢谢。
  • @BN83 正如我已经在两个答案中指出的那样:查询事件表以获取分支列表效率低下,如果您有一个没有任何关联事件的分支,那么该分支将不会出现在报告中。您应该使用您拥有的分支表,而不是 Spencer 建议的解决方案。
【解决方案2】:

有两个问题:

  1. 日历表有完整的日期列表,所以它应该在左连接的左边。

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

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

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

【讨论】:

  • 如果我误解了这一点,请告诉我。我有一个branchs表,但是branchs表中没有数据需要调用得到需要的结果,那为什么还要加入branch表呢?该表只有分支 ID 和分支名称。
  • 因为您想按日期和分支获取计数,包括那些没有任何关联事件的日期和分支组合。
  • 好的,我不知道为什么我需要将它与分支表对齐,因为我的主要结果是每个日期都有一行。难道我不能返回一个 Null 结果,我可以告诉它归类为零吗?
  • 它确实有效,但是添加第二个 where 子句 AND EventType = 2 现在会删除所有零结果。 EventType 没有表格,它只是正在发生的事件类型的简单代码...我没有将其添加到原始问题中,因为我认为它只会过滤结果。
  • 您需要将该过滤条件移动到on 子句中。如果您有任何后续问题,请单独提出。
【解决方案3】:

我会通过使用交叉连接来链接日历中所需的日期来解决此问题。然后将其与 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

【讨论】:

    猜你喜欢
    • 2019-03-25
    • 2015-02-24
    • 2013-11-28
    • 2016-09-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多