33

次のようなクエリを使用して、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 | 

私は非常にぎこちない (そしてほぼ間違いなくバグのある) 回避策を、1 か月あたりの日数の配列といくつかの計算でまとめましたが、mysql または perl 側のいずれかでもっと簡単なものが必要です。

なぜ私がそんなに愚かなのかについて、何か天才的なアイデア/平手打ちはありますか?


いくつかの理由から、問題の日付範囲の一時テーブルを生成するストアド プロシージャを使用することになりました。

  • 毎回検索する日付範囲を知っている
  • 問題のサーバーは、残念ながら atm に perl モジュールをインストールできるものではなく、リモートで何もインストールされていないほど老朽化していました。

Perl の Date/DateTime 反復回答も非常に良かったです。複数の回答を選択できたらいいのにと思います。

4

9 に答える 9

20

サーバー側でそのようなものが必要な場合は、通常、2 つの時点の間のすべての可能な日付を含むテーブルを作成し、このテーブルをクエリ結果と結合します。このようなもの:

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 でない場合は、追加の文字列を追加します。

于 2008-09-16T19:19:20.670 に答える
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;

出力に関しては、手動で CSV を生成する代わりに、SELECT INTO OUTFILEを使用します。特殊文字のエスケープについても心配する必要はありません。

于 2008-09-16T19:06:51.153 に答える
4

これは MySQL が行うことではなく、空の日付値を挿入します。私はこれを perl で 2 段階のプロセスで行います。まず、クエリからすべてのデータを日付別に整理されたハッシュに読み込みます。次に、Date::EzDate オブジェクトを作成し、それを 1 日ずつインクリメントします。

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 を見つけることができます。

于 2008-09-16T19:11:47.227 に答える
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";
}

上記のコードが行うことは、最後に出力された日付を DateTimeobject$dtに保存し、現在の日付が 1 日以上先の場合、次の日付と同じになるまで$dt1 日ずつインクリメントします (そして に 1 行出力します )。CSV現在の日付。

この方法では、追加のテーブルは必要なく、事前にすべての行をフェッチする必要もありません。

于 2008-09-16T19:37:10.283 に答える
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 );
}

うーん、それは私が思っていたよりも複雑であることが判明しました..それが理にかなっていることを願っています!

于 2008-09-16T19:43:41.423 に答える
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 JOINfromOrdinalを実行します。これは、先週の毎日を取得する単純なケースです。

SELECT CURDATE() - INTERVAL `n` DAY AS `day`
FROM `Ordinal` WHERE `n` <= 7
ORDER BY `n` ASC

これについて変更する必要がある 2 つの点は、開始点と間隔です。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;

したがって、過去 3 か月間の 1 日あたりのメッセージ数を取得するために参加する場合、最終的なコードは次のようになります。

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。たとえば、過去 7 週間 (1 ~ 7) の平日 (1 ~ 5) ごとに一覧表示できます。
  • テーブルに日付を格納することで高速化できますがOrdinal、柔軟性が低下します。Ordinalこれにより、何度使用してもテーブルは1 つしか必要ありません。それでも、速度に見合うだけの価値がある場合は、INSERT INTO ... SELECT構文を試してください。
于 2011-05-27T17:57:50.573 に答える
0

推奨される DateTime や Time::Piece (5.10 からのコア) などの日付計算を行うには、いくつかの Perl モジュールを使用します。日付をインクリメントして日付を印刷し、日付が現在と一致するまで0を印刷します。

于 2008-09-16T19:15:06.093 に答える
-1

これが機能するかどうかはわかりませんが、考えられるすべての日付を含む新しいテーブルを作成した場合 (日付の範囲が予期せず変更される場合、このアイデアの問題になる可能性があります...)、次に、2 つのテーブルで左結合を行いますか? 可能性のある日付が膨大にある場合、または最初と最後の日付を予測する方法がない場合、それはクレイジーな解決策だと思いますが、日付の範囲が固定されているか、簡単に計算できる場合、これはうまくいくかもしれません.

于 2008-09-16T19:08:57.590 に答える