次のようなクエリを使用して、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側のどちらかでもっと簡単なものが必要です。
なぜ私がそんなに馬鹿なのか、天才的なアイデアや平手打ちはありますか?
いくつかの理由で、問題の日付範囲の一時テーブルを生成するストアドプロシージャを使用することになりました。
PerlのDate/DateTimeを繰り返す回答も非常に良かったので、複数の回答を選択できればと思います。
サーバー側でそのようなものが必要な場合は、通常、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でない場合は、いくつかの追加文字列を入れてください。
この問題に対処する必要があったとき、不足している日付を入力するために、実際には、関心のあるすべての日付を含む参照テーブルを作成し、日付フィールドのデータテーブルに結合しました。粗雑ですが、機能します。
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 を使用します。特別なキャラクターを逃れる心配もありません。
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
に格納し、現在の日付が1日以上先の場合、$dt
を次のようにインクリメントします。現在の日付と同じになるまで、ある日(そして、それをCSV
に1行出力します)。
このように、余分なテーブルは必要ありません。また、事前にすべての行をフェッチする必要もありません。
ばかげているわけではありません。これはMySQLが行うことではなく、空の日付値を挿入します。私はこれをPerlで2段階のプロセスで行います。まず、クエリのすべてのデータを日付順に整理されたハッシュに読み込みます。次に、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にありませんが、日付の比較を行い、日付の増分を提供する別のPerlmodをおそらく見つけることができます。
私はあなたが残りを理解することを願っています。
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からmax(n3)* 100 + max(n2)* 10 + max(n1)までの数値の列が表示されます
ここでは最大n3が3であるため、SELECTは399に加えて、0-> 400レコード(カレンダーの日付)を返します。
たとえば、min(date)からnow()に制限することで、動的カレンダーを調整できます。
ギャップがどこにあるかわからないが、リストの最初の日付から最後の日付までのすべての値(おそらく)が必要なので、次のようにします。
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 );
}
うーん、それは私が思っていたよりも複雑であることが判明しました..それが理にかなっていることを願っています!
この問題の最も簡単な一般的な解決策は、必要な行数が最も多い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
これについて変更する必要がある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-5)ごとに一覧表示します。Ordinal
テーブルに日付を保存することで高速化できますが、柔軟性は低くなります。このように、何度使用しても、必要なOrdinal
テーブルは1つだけです。それでも、速度に見合うだけの場合は、INSERT INTO ... SELECT
構文を試してください。推奨されるDateTimeやTime :: Piece(5.10からのコア)など、いくつかのPerlモジュールを使用して日付計算を実行します。日付をインクリメントして日付を印刷し、日付が現在と一致するまで0にします。