2008-09-16 34 views
33

我建立从MySQL表的快速CSV与像查询:在sql结果中填充空日期(在mysql或perl结束时)最直接的方法是什么?

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 | 

我打了一个非常尴尬的(几乎肯定是越野车)的解决方法,每天每月和一些数学的数组,但必须有更直接的东西在MySQL或Perl端。

任何天才的想法/拍拍我为什么如此愚蠢的脸?


我结束了其产生的临时表的日期范围问题要问几个原因存储过程会:

  • 我知道我将寻找每一个日期范围时间
  • 有问题的服务器遗憾的是没有一个,我可以安装在ATM上Perl模块,并且它的状态已经足够破旧,它没有任何远程日期:: - Ÿ安装

perl Date/DateTime迭代答案也非常好,我希望我可以选择多个答案!

回答

20

当你需要类似的东西在服务器端,你通常创建一个包含两个时间点之间的所有可能的日期表,然后离开了加入这个表的查询结果。事情是这样的:

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 

在这种特殊情况下,将更好地把客户端上的一个小检查,如果当前的日期是不是previos + 1,把一些另外的字符串。

7

当我不得不处理这个问题时,为了填写缺失的日期,我实际上创建了一个参考表,它只包含我感兴趣的所有日期,并加入了日期字段中的数据表。这是粗糙的,但它的工作原理。

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。我们也不用担心逃脱特殊字符。

-1

我不知道这是否可行,但如果您创建了一个包含所有可能日期的新表格(这可能是这个想法的问题,如果日期范围将发生不可预测的变化)。 ..),然后做两个表上的左连接?如果有大量可能的日期,或者没有办法预测第一个和最后一个日期,我想这是一个疯狂的解决方案,但是如果日期范围是固定的或容易解决的话,那么这可能会起作用。

4

不是愚蠢的,这不是MySQL做的事,插入空的日期值。我通过两步过程在Perl中执行此操作。首先,将查询中的所有数据加载到按日期组织的散列中。然后,我创建了一个日期:: 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++; 
} 

其中最后日期是另一个EzDate对象或包含日期范围的最后一个字符串。

EzDate是不CPAN的权利,但你也许可以找到另外的Perl国防部会做比较日期,并提供一个日期递增器。

0

使用一些Perl模块,做日期计算,比如推荐的日期时间或时间::片(5.10核心)。只需递增日期和打印日期,直到日期将与当前日期匹配。

4

你可以使用一个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最后打印的日期,并且在当前日期 超过一天将来,它以一天递增$dt(并打印一行 CSV),直到它是一样的当前日期。

这样就不需要额外的表,且不需要提前获取所有 行。

1

因为你不知道在哪里的差距,但你希望所有在列表中的第一次约会的最后一个值(大概),这样做:

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); 
} 

嗯,事实证明,这比我想象的要复杂得多..我希望它是有道理的!

1

我认为这个问题最简单的一般解决方案是创建一个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 

接下来,从做一个OrdinalLEFT 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; 

所以最终代码会是这个样子,如果你加入让每天的邮件数量在过去三个月:

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来满足您的所有不间断序列需求。只要确保它包含比最长序列更多的行。
  • 您可以在Ordinal上对多个序列使用多个查询,例如列出过去七(1-7)周的每个工作日(1-5)。
  • 您可以通过在您的Ordinal表中存储日期来加快速度,但它不够灵活。这样,无论您使用多少次,您只需要一张Ordinal表格。不过,如果速度值得,请尝试INSERT INTO ... SELECT语法。
1

我希望你能弄清楚其余的。

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 + MAX(N2)* 10 + MAX(N1)

因为我们在这里将最大n3设置为3,SELECT将返回399,再加上0 - > 400条记录(日历中的日期)。

您可以通过限制动态日历来调整动态日历,例如,从min(日期)到现在()。

相关问题