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 个问题:
日历 table 有完整的日期列表,因此它应该在左联接的左侧。
您不仅需要完整的日期列表,还需要完整的日期-分支组合列表。
我假设您有一个分支 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。当给定 cdate
和 branch
.[=63 没有匹配的 Event
行时,我们需要这些行能够 return "zero" 的计数=]
同样,我们假设 cdate
在 Calendar
中是唯一的,因此我们 return 从 Calendar
开始(最多)五行。我们还假设需要检查 Event
中的(可能)更多行,以将 eventdate
与 cdate
进行比较。我们不想阻止 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
我创建了一个日历 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 个问题:
日历 table 有完整的日期列表,因此它应该在左联接的左侧。
您不仅需要完整的日期列表,还需要完整的日期-分支组合列表。
我假设您有一个分支 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。当给定 cdate
和 branch
.[=63 没有匹配的 Event
行时,我们需要这些行能够 return "zero" 的计数=]
同样,我们假设 cdate
在 Calendar
中是唯一的,因此我们 return 从 Calendar
开始(最多)五行。我们还假设需要检查 Event
中的(可能)更多行,以将 eventdate
与 cdate
进行比较。我们不想阻止 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