我创建了一个日历表,其中只包含大量日期。然后我的事件表有排队的日期,如果一天没有事件,我想为此返回零。我有以下内容:
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
答案 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行,即我们想要返回的行。当给定的Event
和cdate
没有匹配的branch
行时,我们需要这些行能够返回“零”。
同样,我们假设cdate
中的Calendar
是唯一的,因此我们会从Calendar
返回(最多)五行。我们还假设将(可能)需要检查来自Event
的更多行,以便将eventdate
与cdate
进行比较。我们不希望阻止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)
有两个问题:
日历表包含完整的日期列表,因此它应位于左连接的左侧。
您不需要完整的日期列表,您需要完整的日期列表 - 分支组合。
我假设您有一个分支表来存储完整的分支列表。我在日历表上交叉连接,然后将结果连接到实际事件表:
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