在SQL结果中(在mysql或者perl结束时)填充空日期的最直接方法是什么?

时间:2020-03-05 18:56:52  来源:igfitidea点击:

我正在从带有查询的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模块的服务器,并且它的状态已严重不足,无法远程安装Date ::-y

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

解决方案

回答

当我不得不处理这个问题时,为了填写缺少的日期,我实际上创建了一个引用表,其中包含了我感兴趣的所有日期,并在date字段中加入了数据表。这很粗糙,但是行得通。

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要做的事情,它插入了空的日期值。我在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,它将进行日期比较并提供日期增量器。

回答

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

回答

当在服务器端需要类似的内容时,通常会创建一个表,其中包含两个时间点之间的所有可能的日期,然后将该表与查询结果连接起来。像这样的东西:

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,则添加一些添加字符串。

回答

我们可以使用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";
}

上面的代码所做的是将最后打印的日期保存在
日期时间对象$ dt,以及当前日期超过一天
将来,它会将$ dt增加一天(并将其打印到
CSV"),直到与当前日期相同为止。

这样,我们不需要额外的表,也不需要获取所有的表
提前行。

回答

由于我们不知道差距在哪里,但是我们想要从列表中的第一个日期到最后一个日期的所有值(大概),请执行以下操作:

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

嗯,事实证明这比我想象的要复杂。.我希望这是合理的!