在sql结果中填充空日期的最简单方法是什么(在mysql或perl端)?

时间:2008-09-16 19:01:43

标签: mysql perl calendar datediff

我正在用mysql表构建一个快速的csv,其查询如下:

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模块的,而且它的状态是破旧的,它没有任何远程日期:: - y安装

perl Date / DateTime-iterating答案也非常好,我希望我能选择多个答案!

9 个答案:

答案 0 :(得分: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,则添加一些附加字符串。

答案 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。让我们免于担心逃避特殊角色。

答案 2 :(得分:4)

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

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

EzDate现在不在CPAN上,但您可能会找到另一个将进行日期比较并提供日期增量的perl mod。

答案 3 :(得分: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";
}

以上代码的作用是将最后打印的日期保存在a中 DateTime对象$dt,当前日期超过一天 在将来,它会将$dt增加一天(并将其打印到一行 CSV)直到它与当前日期相同。

这样您就不需要额外的表,也不需要获取所有表 提前排。

答案 4 :(得分:2)

我希望你能弄清楚其余部分。

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)的列

由于这里我们将max n3设为3,SELECT将返回399,加上0 - &gt; 400条记录(日历中的日期)。

您可以通过限制动态日历来调整动态日历,例如,从现在的最小(日期)()开始。

答案 5 :(得分: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 );
}
嗯,事实证明它比我想象的要复杂得多..我希望它有意义!

答案 6 :(得分: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

接下来,从LEFT JOIN对您的数据进行Ordinal。这是一个简单的案例,在上周获得每一天:

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语法。

答案 7 :(得分:0)

使用一些Perl模块进行日期计算,例如推荐的DateTime或Time :: Piece(来自5.10的核心)。只需增加日期和打印日期,0直到日期与当前匹配。

答案 8 :(得分:-1)

我不知道这是否有效,但是如果你创建了一个包含所有可能日期的新表(如果日期范围不可预测地改变可能是这个想法的问题,那该怎么办... 。)然后在两个表上进行左连接?我想这是一个疯狂的解决方案,如果有大量的可能日期,或无法预测第一个和最后一个日期,但如果日期范围是固定的或易于解决,那么这可能会有效。