6

这是我的 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 的日子里,我也希望显示一行。现在我错过了大量没有活动的日子。

关于如何更改此查询以做到这一点的任何想法?

4

4 回答 4

9

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,所以你必须做更多的体操才能加入表格)。

于 2012-04-05T19:23:33.187 回答
8

大概的概念

在 MySQL 中生成数据有两种主要方法。一种是在运行查询时动态生成数据,另一种是将数据保存在数据库中并在必要时使用它。当然,如果您要经常运行查询,第二个会比第一个快。但是,第二个将需要数据库中的一个表,其唯一目的是生成丢失的数据。它还要求您具有足够的权限来创建该表。

动态数据生成

这种方法涉及使UNIONs 生成一个可用于连接实际表的假表。可怕且重复的查询是:

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变量的过滤器来微调日期时间段(但不要使用比您使用笛卡尔积创建的时间段更长的时间段)。

静态数据生成

此解决方案将要求您在数据库中生成一个表。该方法与前一种方法类似。您必须首先将数据插入该表:整数范围从1XwhereX是最大所需范围。同样,如果您不确定只需插入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并且我们希望查看每天birthDate5几天所有的计数,并且如果计数显示该值,则可以运行以下查询:20120

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)而不是硬编码您可以查询的日期minmax域表中的值,如下所示:

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日期范围更具可读性。这是动态解决方案的小提琴,因此您也可以使用它。

希望这可以帮助!

于 2012-04-10T00:28:06.103 回答
1

在一个查询中执行此操作的方法:

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');
于 2012-04-13T02:01:08.803 回答
0

试验台:

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;
于 2012-04-12T16:31:14.953 回答