什么是最简单的方法来填补空的date在SQL结果(在MySQL或Perl结束)?
我正在build立一个快速的csv从一个查询如下的MySQL表:
select DATE(date),count(date) from table group by DATE(date) order by date asc;
然后把它们倾倒在perl文件中:
while(my($date,$sum) = $sth->fetchrow) { print CSV "$date,$sum\n" }
数据中有数据缺口,但是:
| 2008-08-05 | 4 | | 2008-08-07 | 23 |
我想填充数据填写零计数条目的最后几天结束:
| 2008-08-05 | 4 | | 2008-08-06 | 0 | | 2008-08-07 | 23 |
我打了一个非常尴尬(几乎肯定是越野车)的解决方法,每个月的数组和一些math,但是必须有更直接的东西在MySQL或Perl的一面。
任何天才的想法/一巴掌,为什么我这么笨?
我结束了一个存储过程,其中产生了一个临时表的date范围有几个原因:
- 我知道我每次都在寻找的date范围
- 有问题的服务器不幸的是我可以在atm上安装perl模块,并且它的状态已经够老了,它没有任何东西Date :: – y已安装
perldate/date时间 – 迭代答案也非常好,我希望我可以select多个答案!
当你在服务器端需要类似的东西时,你通常会创build一个包含两个时间点之间的所有可能date的表,然后将这个表与查询结果连接起来。 像这样的东西:
create procedure sp1(d1 date, d2 date) declare d datetime; create temporary table foo (d date not null); set d = d1 while d <= d2 do insert into foo (d) values (d) set d = date_add(d, interval 1 day) end while select foo.d, count(date) from foo left join table on foo.d = table.date group by foo.d order by foo.d asc; drop temporary table foo; end procedure
在这种情况下,最好在客户端进行一些检查,如果当前date不是previos + 1,则需要添加一些附加string。
当我不得不处理这个问题时,为了填写缺失的date,我实际上创build了一个只包含我感兴趣的所有date的参考表,并在date字段中join了数据表。 这是粗糙的,但它的作品。
SELECT DATE(r.date),count(d.date) FROM dates AS r LEFT JOIN table AS d ON d.date = r.date GROUP BY DATE(r.date) ORDER BY r.date ASC;
至于输出,我只是使用SELECT INTO OUTFILE而不是手动生成CSV。 让我们摆脱了担心逃离特殊字符的担忧。
不是愚蠢的,这不是MySQL所做的,插入空的date值。 我在perl中这样做了两步。 首先,将查询中的所有数据加载到按date组织的散列中。 然后,我创build一个Date :: EzDate对象,并增加它的一天,所以…
my $current_date = Date::EzDate->new(); $current_date->{'default'} = '{YEAR}-{MONTH NUMBER BASE 1}-{DAY OF MONTH}'; while ($current_date <= $final_date) { print "$current_date\t|\t%hash_o_data{$current_date}"; # EzDate provides for automatic stringification in the format specfied in 'default' $current_date++; }
最终date是另一个EzDate对象或包含date范围结束的string。
EzDate现在不在CPAN上,但是你可能会发现另外一个perl mod将会做date比较并提供一个date增加器。
你可以使用一个DateTime对象:
use DateTime; my $dt; while ( my ($date, $sum) = $sth->fetchrow ) { if (defined $dt) { print CSV $dt->ymd . ",0\n" while $dt->add(days => 1)->ymd lt $date; } else { my ($y, $m, $d) = split /-/, $date; $dt = DateTime->new(year => $y, month => $m, day => $d); } print CSV, "$date,$sum\n"; }
上面的代码所做的是保持DateTime
对象$dt
存储的最后一个打印date,并且当前date将来超过一天时,它会将$dt
增加一天(并将其打印为一行)直到它与当前date相同。
这样你不需要额外的表格,也不需要预先提取所有的行。
既然你不知道差距在哪里,但是你需要从列表中的第一个date到最后一个date的所有值(大概是这样),请执行如下操作:
use DateTime; use DateTime::Format::Strptime; my @row = $sth->fetchrow; my $countdate = strptime("%Y-%m-%d", $firstrow[0]); my $thisdate = strptime("%Y-%m-%d", $firstrow[0]); while ($countdate) { # keep looping countdate until it hits the next db row date if(DateTime->compare($countdate, $thisdate) == -1) { # counter not reached next date yet print CSV $countdate->ymd . ",0\n"; $countdate = $countdate->add( days => 1 ); $next; } # countdate is equal to next row's date, so print that instead print CSV $thisdate->ymd . ",$row[1]\n"; # increase both @row = $sth->fetchrow; $thisdate = strptime("%Y-%m-%d", $firstrow[0]); $countdate = $countdate->add( days => 1 ); }
嗯,结果比我想象的要复杂得多..我希望这是有道理的!
我认为这个问题最简单的通用解决scheme是创build一个你需要的行数最多的Ordinal
表(在你的情况下,31 * 3 = 93)。
CREATE TABLE IF NOT EXISTS `Ordinal` ( `n` int(10) unsigned NOT NULL AUTO_INCREMENT, PRIMARY KEY (`n`) ); INSERT INTO `Ordinal` (`n`) VALUES (NULL), (NULL), (NULL); #etc
接下来,从Ordinal
执行一个LEFT JOIN
连接到你的数据。 这是一个简单的例子,在上周每天都在进行:
SELECT CURDATE() - INTERVAL `n` DAY AS `day` FROM `Ordinal` WHERE `n` <= 7 ORDER BY `n` ASC
你需要改变的两件事是起点和间隔。 为了清晰起见,我使用了SET @var = 'value'
语法。
SET @end = CURDATE() - INTERVAL DAY(CURDATE()) DAY; SET @begin = @end - INTERVAL 3 MONTH; SET @period = DATEDIFF(@end, @begin); SELECT @begin + INTERVAL (`n` + 1) DAY AS `date` FROM `Ordinal` WHERE `n` < @period ORDER BY `n` ASC;
所以最后的代码看起来是这样的,如果你在过去的三个月里join了每天的消息数量:
SELECT COUNT(`msg`.`id`) AS `message_count`, `ord`.`date` FROM ( SELECT ((CURDATE() - INTERVAL DAY(CURDATE()) DAY) - INTERVAL 3 MONTH) + INTERVAL (`n` + 1) DAY AS `date` FROM `Ordinal` WHERE `n` < (DATEDIFF((CURDATE() - INTERVAL DAY(CURDATE()) DAY), ((CURDATE() - INTERVAL DAY(CURDATE()) DAY) - INTERVAL 3 MONTH))) ORDER BY `n` ASC ) AS `ord` LEFT JOIN `Message` AS `msg` ON `ord`.`date` = `msg`.`date` GROUP BY `ord`.`date`
提示和意见:
- 查询中最困难的部分可能是确定限制
Ordinal
时使用的天Ordinal
。 相比之下,将整数序列转换为date很容易。 - 你可以使用
Ordinal
来满足你所有的不间断的需求。 只要确保它包含比您最长的序列更多的行。 - 您可以在
Ordinal
上使用多个查询来查看多个序列,例如,在过去七(1-7)周的每个工作日(1-5)中列出。 - 您可以通过在
Ordinal
表中存储date来加快速度,但是它会变得不那么灵活。 这样你只需要一个Ordinal
表,不pipe你使用了多less次。 不过,如果速度值得,请尝试INSERT INTO ... SELECT
语法。
我希望你能弄清楚其余的。
select * from ( select date_add('2003-01-01 00:00:00.000', INTERVAL n5.num*10000+n4.num*1000+n3.num*100+n2.num*10+n1.num DAY ) as date from (select 0 as num 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) n1, (select 0 as num 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) n2, (select 0 as num 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) n3, (select 0 as num 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) n4, (select 0 as num 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) n5 ) a where date >'2011-01-02 00:00:00.000' and date < NOW() order by date
同
select n3.num*100+n2.num*10+n1.num as date
你会得到一个从0到最大(n3)* 100 +最大(n2)* 10 +最大(n1)
由于这里我们有最大n3为3,SELECT将返回399,再加上0 – > 400条logging(日历中的date)。
您可以通过限制它来调整dynamic日历,例如,从min(date)到现在()。
使用一些Perl模块来进行date计算,如推荐的DateTime或Time :: Piece(核心从5.10)。 只是递增date和打印date,直到date为0才符合当前。
我不知道这是否可行,但如果你创build了一个包含所有可能date的新表(这可能是这个想法的问题,如果date范围将不可预知地变化),那么怎么办?然后做两个表上的左连接? 我想这是一个疯狂的解决scheme,如果有大量的可能的date,或者没有办法预测第一个和最后一个date,但是如果date的范围是固定的或容易解决,那么这可能工作。