这是我的 SQL:
SELECT
COUNT(id),
CONCAT(YEAR(created_at), '-', MONTH(created_at), '-', DAY(created_at))
FROM my_table
GROUP BY YEAR(created_at), MONTH(created_at), DAY(created_at)
即使在没有创建 ID 的日子里,我也希望显示一行。现在我错过了大量没有活动的日子。
关于如何更改此查询以做到这一点的任何想法?
SQL 在返回不在数据库中的数据方面是出了名的糟糕。您可以找到日期间隔的开始值和结束值,但很难获取所有日期。
解决方案是创建一个日历表,其中每个日期都有一条记录,然后将其外连接到您的查询中。
这是一个假设 created_at 是 DATE 类型的示例:
SELECT calendar_date, COUNT(`id`)
FROM calendar LEFT OUTER JOIN my_table ON calendar.calendar_date = my_table.created_at
GROUP BY calendar_date
(我猜 created_at 确实是 DATETIME,所以你必须做更多的体操才能加入表格)。
大概的概念
在 MySQL 中生成数据有两种主要方法。一种是在运行查询时动态生成数据,另一种是将数据保存在数据库中并在必要时使用它。当然,如果您要经常运行查询,第二个会比第一个快。但是,第二个将需要数据库中的一个表,其唯一目的是生成丢失的数据。它还要求您具有足够的权限来创建该表。
动态数据生成
这种方法涉及使UNION
s 生成一个可用于连接实际表的假表。可怕且重复的查询是:
select aDate from (
select @maxDate - interval (a.a+(10*b.a)+(100*c.a)+(1000*d.a)) day aDate from
(select 0 as a union all select 1 union all select 2 union all select 3
union all select 4 union all select 5 union all select 6 union all
select 7 union all select 8 union all select 9) a, /*10 day range*/
(select 0 as a union all select 1 union all select 2 union all select 3
union all select 4 union all select 5 union all select 6 union all
select 7 union all select 8 union all select 9) b, /*100 day range*/
(select 0 as a union all select 1 union all select 2 union all select 3
union all select 4 union all select 5 union all select 6 union all
select 7 union all select 8 union all select 9) c, /*1000 day range*/
(select 0 as a union all select 1 union all select 2 union all select 3
union all select 4 union all select 5 union all select 6 union all
select 7 union all select 8 union all select 9) d, /*10000 day range*/
(select @minDate := '2001-01-01', @maxDate := '2002-02-02') e
) f
where aDate between @minDate and @maxDate
无论如何,它比看起来简单。它使用10
数值生成派生表的笛卡尔积,因此结果将包含查询中派生表数量的10^X
行。X
在此示例中,有10000
日期范围,因此您可以表示27
多年的时间段。如果您需要更多,UNION
请在查询中添加另一个并更新间隔,如果您不需要太多,您可以UNION
从派生表中删除 s 或单个值。澄清一下,您可以通过应用带有WHERE
子句@minDate
和@maxDate
变量的过滤器来微调日期时间段(但不要使用比您使用笛卡尔积创建的时间段更长的时间段)。
静态数据生成
此解决方案将要求您在数据库中生成一个表。该方法与前一种方法类似。您必须首先将数据插入该表:整数范围从1
到X
whereX
是最大所需范围。同样,如果您不确定只需插入100000
值,您就可以创建273
多年的日期范围。因此,一旦获得整数序列,就可以将其转换为如下日期范围:
select '2012-01-01' + interval value - 1 day aDay from seq
having aDay <= '2012-01-05'
假设一个名为 的表seq
名为value
. 顶部是起始日期,底部是截止日期。
把它变成有用的东西
好的,现在我们生成了日期周期,但我们仍然缺少一种查询数据并将缺失值显示为实际值的方法0
。这就是left join
救援的地方。为了确保我们都在同一个页面上,aleft join
与 an 相似,inner join
但只有一个区别:它将保留连接的左表中的所有记录,而不管右表是否有匹配的记录. 换句话说, aninner join
将删除连接中所有不匹配的行,而left join
将保留左表中的行,并且对于在右表中没有匹配记录的左侧记录,left join
将填充该“空间”有一个null
价值。
因此,我们应该将我们的域表(具有“缺失”数据的表)与我们新生成的表连接起来,将后者放在连接的左侧,将前者放在右侧,以便考虑所有元素,无论它们是否存在在域表中。
例如,如果我们有一个domainTable
包含字段的表,ID, birthDate
并且我们希望查看每天birthDate
前5
几天所有的计数,并且如果计数显示该值,则可以运行以下查询:2012
0
select allDays.aDay, count(dt.id) from (
select '2012-01-01' + interval value - 1 day aDay from seq
having aDay <= '2012-01-05'
) allDays
left join domainTable dt on allDays.aDay = dt.birthDate
group by allDays.aDay
这会生成一个包含所有必需天数的派生表(注意我正在使用静态数据生成)并left join
针对我们的域表执行一个,因此所有天都将被显示,无论它们在我们的域表中是否具有匹配值。另请注意,count
应在具有null
不计入值的字段上完成。
需要考虑的注意事项
1) 查询可用于查询其他时间间隔(月、年)对代码进行小的更改
2)而不是硬编码您可以查询的日期min
和max
域表中的值,如下所示:
select (select min(aDate) from domainTable) + interval value - 1 day aDay
from seq
having aDay <= (select max(aDate) from domainTable)
这将避免生成不必要的记录。
实际回答你的问题
我想你应该已经弄清楚如何做你想做的事了。无论如何,这里有一些步骤,以便其他人也可以从中受益。首先,创建整数表。其次,运行这个查询:
select allDays.aDay, count(mt.id) aCount from (
select (select date(min(created_at)) from my_table) + interval value - 1 day aDay
from seq s
having aDay <= (select date(max(created_at)) from my_table)
) allDays
left join my_table mt on allDays.aDay = date(mt.created_at)
group by allDays.aDay
我想created_at
是一个日期时间,这就是你以这种方式连接的原因。然而,这恰好是 MySQL 本地存储日期的方式,所以我只是按日期字段分组,但将其created_at
转换为实际date
数据类型。你可以用这个fiddle来玩它。
这是动态生成数据的解决方案:
select allDays.aDay, count(mt.id) aCount from (
select @maxDate - interval a.a day aDay from
(select 0 as a union all select 1 union all select 2 union all select 3
union all select 4 union all select 5 union all select 6 union all
select 7 union all select 8 union all select 9) a, /*10 day range*/
(select @minDate := (select date(min(created_at)) from my_table),
@maxDate := (select date(max(created_at)) from my_table)) e
where @maxDate - interval a.a day between @minDate and @maxDate
) allDays
left join my_table mt on allDays.aDay = date(mt.created_at)
group by allDays.aDay
如您所见,查询的框架与前一个相同。唯一改变的是派生表allDays
的生成方式。现在,派生表的生成方式也和我之前添加的略有不同。这是因为在示例文件中,我只需要一个10
-day 范围。如您所见,它比添加1000
日期范围更具可读性。这是动态解决方案的小提琴,因此您也可以使用它。
希望这可以帮助!
在一个查询中执行此操作的方法:
SELECT COUNT(my_table.id) AS total,
CONCAT(YEAR(dates.ddate), '-', MONTH(dates.ddate), '-', DAY(dates.ddate))
FROM (
-- Creates "on the fly" 65536 days beginning from 2000-01-01 (179 years)
SELECT DATE_ADD("2000-01-01", INTERVAL (b1.b + b2.b + b3.b + b4.b + b5.b + b6.b + b7.b + b8.b + b9.b + b10.b + b11.b + b12.b + b13.b + b14.b + b15.b + b16.b) DAY) AS ddate FROM
(SELECT 0 AS b UNION SELECT 1) b1,
(SELECT 0 AS b UNION SELECT 2) b2,
(SELECT 0 AS b UNION SELECT 4) b3,
(SELECT 0 AS b UNION SELECT 8) b4,
(SELECT 0 AS b UNION SELECT 16) b5,
(SELECT 0 AS b UNION SELECT 32) b6,
(SELECT 0 AS b UNION SELECT 64) b7,
(SELECT 0 AS b UNION SELECT 128) b8,
(SELECT 0 AS b UNION SELECT 256) b9,
(SELECT 0 AS b UNION SELECT 512) b10,
(SELECT 0 AS b UNION SELECT 1024) b11,
(SELECT 0 AS b UNION SELECT 2048) b12,
(SELECT 0 AS b UNION SELECT 4096) b13,
(SELECT 0 AS b UNION SELECT 8192) b14,
(SELECT 0 AS b UNION SELECT 16384) b15,
(SELECT 0 AS b UNION SELECT 32768) b16
) dates
LEFT JOIN my_table ON dates.ddate = my_table.created_at
GROUP BY dates.ddate
ORDER BY dates.ddate
仅当您要测试并且没有在问题上指示“my_table”时才需要下一个代码:
create table `my_table` (
`id` int (11),
`created_at` date
);
insert into `my_table` (`id`, `created_at`) values('1','2000-01-01');
insert into `my_table` (`id`, `created_at`) values('2','2000-01-01');
insert into `my_table` (`id`, `created_at`) values('3','2000-01-01');
insert into `my_table` (`id`, `created_at`) values('4','2001-01-01');
insert into `my_table` (`id`, `created_at`) values('5','2100-06-06');
试验台:
create table testbed (id integer, created_at date);
insert into testbed values
(1, '2012-04-01'),
(1, '2012-04-30'),
(2, '2012-04-02'),
(3, '2012-04-03'),
(3, '2012-04-04'),
(4, '2012-04-04');
我也使用any_table
,我像这样人工创建的:
create table any_table (id integer);
insert into any_table values (1), (2), (3), (4), (5), (6), (7), (8), (9), (10);
insert into any_table select * from any_table; -- repeat this insert 7-8 times
您可以使用数据库中的任何表,只要该表的行数超过max(created_dt) - min(created_dt)
范围,至少 365 行才能涵盖一年。
询问:
SELECT concat(year(dr._date),'-',month(dr._date),'-',day(dr._date)),
-- or, instead of concat(), simply: dr._date
count(id)
FROM (
SELECT date_add(r.mindt, INTERVAL @dist day) _date,
@dist := @dist + 1 AS days_away
FROM any_table t
JOIN (SELECT min(created_at) mindt,
max(created_at) maxdt,
@dist := 0
FROM testbed) r
WHERE date_add(r.mindt, INTERVAL @dist day) <= r.maxdt) dr
LEFT JOIN testbed tb ON dr._date = tb.created_at
GROUP BY dr._date;