如何在MySQL中生成数据?
这是我的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)
即使在没有创buildID的日子里,我也想要显示一行。 现在我错过了几天没有活动的日子。
任何想法如何改变这个查询来做到这一点?
在一个查询中做到这一点的方法:
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
下一个代码只有在你想testing的时候才需要,而且没有问题中指出的“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');
SQL在返回不在数据库中的数据时出了名的问题。 你可以finddate差距的开始和结束值,但是获取所有date是困难的。
解决方法是创build一个日历表,每个date有一个logging,OUTER将其添加到您的查询中。
这是一个假设created_at是DATEtypes的示例:
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,所以你必须做更多的体操来join桌子)。
大概的概念
在MySQL中生成数据主要有两种方法。 一种是在运行查询时dynamic生成数据,另一种是在数据库中使用数据库,并在必要时使用它。 当然,如果你要经常运行你的查询,第二个会比第一个快。 但是,第二个将需要在数据库中的一个表,其目的只是生成缺失的数据。 它也将要求你有足够的权限来创build该表。
dynamic数据生成
这种方法包括让UNION
生成一个可以用来连接实际表的假表。 可怕和重复的查询是:
select aDate from ( select @maxDate - interval (a.a+(10*ba)+(100*ca)+(1000*da)) 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
或单个值。 为了澄清,您可以通过在@minDate
和@maxDate
variables上应用带WHERE
子句的filter(但不要使用比使用笛卡尔积创build的更长的句点)来微调date。
静态数据生成
该解决scheme将要求您在数据库中生成一个表格。 该方法与前一个类似。 您必须先将数据插入该表中:范围从1
到X
的整数范围,其中X
是所需的最大范围。 同样,如果您不确定插入100000
值,您将能够创build超过273
年的date范围。 所以,一旦你得到了整数序列,你可以把它转换成这样的date范围:
select '2012-01-01' + interval value - 1 day aDay from seq having aDay <= '2012-01-05'
假设名为seq
的表名为value
。 在起始date和最后的date。
把它变成有用的东西
好的,现在我们已经生成了我们的date时间,但是我们仍然缺less查询数据的方法,并将缺失的值显示为实际的0
。 这是left join
救援的地方。 为了确保我们都在同一页面上, left join
类似于inner join
但是只有一点区别:它将保留连接左表中的所有logging,而不pipe是否存在匹配的logging右边的表。 换句话说, inner join
将删除连接上的所有不匹配的行,而left join
将保留left join
的连接,而对于left join
没有匹配logging的左边的logging, left join
将用null
值填充该“空格”。
因此,我们应该join我们的域表(数据丢失的数据)与我们新生成的表,把后者放在连接的左边,前者放在右边,这样所有的元素都被考虑了,不pipe他们的存在在域表中。
例如,如果我们有一个带有字段ID, birthDate
的表domainTable
ID, birthDate
并且我们希望在birthDate
年的前5
天内看到所有birthDate
的计数,并且如果计数为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
应该在没有count
的字段上进行。
需要考虑的注意事项
1)查询可用于查询对代码进行小改动的其他间隔(月,年)
2)而不是硬编码的date,你可以从域表中查询min
和max
,如下所示:
select (select min(aDate) from domainTable) + interval value - 1 day aDay from seq having aDay <= (select max(aDate) from domainTable)
这将避免生成更多的logging。
其实回答你的问题
我想你应该已经想出了如何去做你想做的事情。 无论如何,这是其他人可以从中受益的步骤。 首先,创build整数表 。 其次,运行这个查询:
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
是一个date时间,这就是为什么你连接这种方式。 然而,这恰好是MySQL本身存储date的方式,所以我只是按date字段分组,但将created_at
转换为实际的date
数据types。 你可以使用这个小提琴玩它。
这里是dynamic生成数据的解决scheme:
select allDays.aDay, count(mt.id) aCount from ( select @maxDate - interval aa 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 aa day between @minDate and @maxDate ) allDays left join my_table mt on allDays.aDay = date(mt.created_at) group by allDays.aDay
正如你所看到的查询的骨架是一样的前一个。 唯一改变的是如何生成派生表allDays
。 现在,生成派生表的方式也与我之前添加的稍有不同。 这是因为在例子filddle我只需要一个10
天的范围。 正如你所看到的,它比增加1000
天的范围更具可读性。 这里是dynamic解决scheme的小提琴 ,以便您也可以玩。
希望这可以帮助!
testing平台:
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
,这是我这样人为创build的:
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)
范围,至less365年覆盖一年。
查询:
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;