为不存在的数据返回空行

Posted

技术标签:

【中文标题】为不存在的数据返回空行【英文标题】:return empty rows for not existsting data 【发布时间】:2020-01-20 14:55:11 【问题描述】:

好的,我有一个包含 date 列和 integer 列的表,我想检索所有行 在特定日期范围内按日期分组;由于不是每天都有行,是否可以让mysql以默认值返回那些天的行?

示例

源表:

date         value
2020-01-01   1
2020-01-01   2
2020-01-03   2
2020-01-07   3
2020-01-08   4
2020-01-08   1

grouping 按日期和summing 值之后的标准行为:

2020-01-01   3
2020-01-03   2
2020-01-07   3
2020-01-08   5

具有空行的期望行为/结果:

2020-01-01   3
2020-01-02   0
2020-01-03   2
2020-01-04   0
2020-01-05   0
2020-01-06   0
2020-01-07   3
2020-01-08   5

【问题讨论】:

我觉得这个问题可以帮你解决你的问题:***.com/questions/2157282/… 【参考方案1】:

您可以执行以下操作:

# table creation:

drop table if exists test_table;

create table test_table (your_date date, your_value int(11));
insert into test_table (your_date, your_value) values ('2020-01-01', 1);
insert into test_table (your_date, your_value) values ('2020-01-01', 2);
insert into test_table (your_date, your_value) values ('2020-01-03', 2);
insert into test_table (your_date, your_value) values ('2020-01-07', 3);
insert into test_table (your_date, your_value) values ('2020-01-08', 4);
insert into test_table (your_date, your_value) values ('2020-01-08', 1);

这将创建一个基本上所有日期的列表。然后,您可以筛选出您感兴趣的日期,加入您的餐桌和小组。

您还可以将 where 语句中的日期替换为子查询(表的最小和最大日期)以使其动态

这是一种变通方法,但确实有效。

select sbqry.base_date, sum(ifnull(t.your_value, 0))
from (select adddate('1970-01-01',t4.i*10000 + t3.i*1000 + t2.i*100 + t1.i*10 + t0.i) base_date from
    (select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t0,
    (select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t1,
    (select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t2,
    (select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t3,
    (select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t4) sbqry
left join test_table t on base_date = t.your_date
where sbqry.base_date between '2020-01-01' and '2020-01-08'
group by sbqry.base_date;

输入:

+------------+------------+
| your_date  | your_value |
+------------+------------+
| 2020-01-01 |          1 |
| 2020-01-01 |          2 |
| 2020-01-03 |          2 |
| 2020-01-07 |          3 |
| 2020-01-08 |          4 |
| 2020-01-08 |          1 |
+------------+------------+

输出:

+------------+------------------------------+
| base_date  | sum(ifnull(t.your_value, 0)) |
+------------+------------------------------+
| 2020-01-01 |                            3 |
| 2020-01-02 |                            0 |
| 2020-01-03 |                            2 |
| 2020-01-04 |                            0 |
| 2020-01-05 |                            0 |
| 2020-01-06 |                            0 |
| 2020-01-07 |                            3 |
| 2020-01-08 |                            5 |
+------------+------------------------------+

【讨论】:

它可以工作,但它不能很好地扩展,因为我事先不知道间隔,如果存在这样的功能,我正在寻找更程序化/自动的东西 如何确定?可能你可以用一个子查询替换 where 语句的硬编码部分.. 抱歉,我看错了你的代码,它完全符合我的需要,即使我发现它背后的逻辑很难理解,谢谢 @fudo FROM 部分中的大子查询生成从“1970-01-01”开始的每个日期,包括接下来的 100,000 天。【参考方案2】:

您还可以使用以下查询来实现您想要的,这可能更容易理解:

SELECT
     date_table.date,
     IFNULL(SUM(value),0) as sum_val
FROM (
     SELECT DATE_ADD('2020-01-01', INTERVAL (@i:=@i+1)-1 DAY) AS `date`
     FROM information_schema.columns,(SELECT @i:=0) gen_sub
     WHERE DATE_ADD('2020-01-01',INTERVAL @i DAY) BETWEEN '2020-01-01' AND '2020-01-08'
) date_table
LEFT JOIN test ON test.date_value = date_table.date
GROUP BY date;

FIND A DEMO HERE

您可以设置一些变量来修复最小和最大日期:

SET @date_min = '2020-01-01';
SET @date_max = '2020-01-08';

SELECT DATE_ADD(@date_min, INTERVAL (@i:=@i+1)-1 DAY) AS `date`
FROM information_schema.columns, (SELECT @i:=0) gen_sub
WHERE DATE_ADD(@date_min, INTERVAL @i DAY) BETWEEN @date_min AND @date_max

一些解释:

事实上,您的问题鼓励我们生成一组日期,因为我们正在寻找具有连续日期集的“左连接”“您的表”,以便匹配“您的表”中没有记录的日期。

由于有 generate_series 函数,这在 PostgreSQL 中会很容易,但在 MySQL 中并不容易,因为不存在这样一个有用的函数。这就是为什么我们需要聪明。

这里的两种解决方案背后都有相同的逻辑:我的意思是它们都为加入另一个表的每一行增加一个日期值(每天),我们称之为“源表”。在上面的答案(不是我的)中,“源表”由许多联合和交叉连接组成(它生成 100k 行),在我的例子中,“源表”是“information_schema.columns”,它已经包含很多行(1800 +)。

在上述情况下,初始日期固定为 1970-01-01,然后它将将此日期递增 100 000 次,以便拥有一组从 1970-01-01 开始的 100 000 个日期。

在我的情况下,初始日期固定为您的最小范围日期 2020-01-01,然后它将为 information_schema.columns 中找到的每一行增加此日期,因此大约 1800 次。您将以一组从 2020-01-01 开始的大约 1800 个日期结束。

最后,您可以将生成的日期集(无论以何种方式)加入您的表格,以便在您想要的范围内为每一天求和(值)。

希望这能帮助您理解这两个查询背后的逻辑;)

【讨论】:

是的,这是一种更程序化的方法 你能解释一下你的回答的逻辑吗?我正在尝试重现它以创建单个整数行,其值在 0n 之间,但我很挣扎 你到底想达到什么目的?您只想要 sum(value) 介于 0 和 n 之间的日期吗?没看懂 我复制粘贴了你的原始答案,因为它完全符合我的需要,我只是试图理解它的逻辑,制作一个更简单的版本,返回 n 行,递增值从 a 到 @ 987654328@;我设法做到了,但我仍然想知道它是如何工作的,尤其是为什么 information_schema.columns 参与其中 你现在可以在上面找到我的解释了!

以上是关于为不存在的数据返回空行的主要内容,如果未能解决你的问题,请参考以下文章

为不存在的表创建视图

SetUbiquitous 为不存在的文件显示“文件已存在”

为不存在的表 slick scala (Slick 3.0.0, scala) 创建一个类 Table

为啥打字稿将联合中的属性标记为不存在?

为不存在的文件解析相对路径(如 realpath)的最佳方法是啥?

Spring MVC 3:为不存在的方法找到不明确的映射