日付とスコアの2つの列を持つテーブルがあります。過去30日間に1つずつ、最大30のエントリがあります。
date score
-----------------
1.8.2010 19
2.8.2010 21
4.8.2010 14
7.8.2010 10
10.8.2010 14
私の問題は、いくつかの日付が欠落していることです-私は見たいです:
date score
-----------------
1.8.2010 19
2.8.2010 21
3.8.2010 0
4.8.2010 14
5.8.2010 0
6.8.2010 0
7.8.2010 10
...
単一のクエリから必要なのは、19,21,9,14,0,0,10,0,0,14 ...を取得することです。つまり、欠落している日付は0で埋められます。
私はすべての値を取得する方法を知っており、サーバー側の言語で日付を繰り返して空白を欠いています。しかし、mysqlでこれを行うことは可能です。そのため、結果を日付でソートし、不足している部分を取得します。
編集:このテーブルにはUserIDという名前の別の列があるので、30.000人のユーザーがいて、そのうちのいくつかはこのテーブルにスコアがあります。各ユーザーの最後の30日間のスコアが必要なため、日付が30日未満の場合は毎日日付を削除します。その理由は、過去30日間のユーザーアクティビティのグラフを作成しており、チャートをプロットするには、カンマで区切られた30個の値が必要だからです。そのため、クエリでUSERID = 10203アクティビティを取得すると言うことができ、クエリは過去30日間に1つずつ、30のスコアを取得します。今より明確になればいいのですが。
MySQLには再帰的な機能がないため、NUMBERSテーブルトリックを使用する必要があります-
インクリメントする数値のみを保持するテーブルを作成します-auto_incrementを使用して簡単に実行できます。
DROP TABLE IF EXISTS `example`.`numbers`;
CREATE TABLE `example`.`numbers` (
`id` int(10) unsigned NOT NULL auto_increment,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
以下を使用してテーブルに入力します。
INSERT INTO `example`.`numbers`
( `id` )
VALUES
( NULL )
...必要な数だけの値。
DATE_ADD を使用して日付のリストを作成し、NUMBERS.id値に基づいて日数を増やします。 「2010-06-06」と「2010-06-14」をそれぞれの開始日と終了日で置き換えます(ただし、同じ形式、YYYY-MM-DDを使用します)-
SELECT `x`.*
FROM (SELECT DATE_ADD('2010-06-06', INTERVAL `n`.`id` - 1 DAY)
FROM `numbers` `n`
WHERE DATE_ADD('2010-06-06', INTERVAL `n`.`id` -1 DAY) <= '2010-06-14' ) x
時間の部分に基づいて、データの表に左から参加します。
SELECT `x`.`ts` AS `timestamp`,
COALESCE(`y`.`score`, 0) AS `cnt`
FROM (SELECT DATE_FORMAT(DATE_ADD('2010-06-06', INTERVAL `n`.`id` - 1 DAY), '%m/%d/%Y') AS `ts`
FROM `numbers` `n`
WHERE DATE_ADD('2010-06-06', INTERVAL `n`.`id` - 1 DAY) <= '2010-06-14') x
LEFT JOIN TABLE `y` ON STR_TO_DATE(`y`.`date`, '%d.%m.%Y') = `x`.`ts`
日付形式を維持する場合は、 DATE_FORMAT関数 を使用します。
DATE_FORMAT(`x`.`ts`, '%d.%m.%Y') AS `timestamp`
カレンダーテーブルを使用してこれを実現できます。これは、一度作成して日付範囲を埋めるテーブルです(例:2000-2050の各日ごとに1つのデータセット。データに依存します)。その後、カレンダーテーブルに対してテーブルの外部結合を作成できます。テーブルに日付がない場合、スコアに0を返します。
私はテーブルの作成などを必要とする他の回答のファンではありません。このクエリは、ヘルパーテーブルなしで効率的に実行します。
SELECT
IF(score IS NULL, 0, score) AS score,
b.Days AS date
FROM
(SELECT a.Days
FROM (
SELECT curdate() - INTERVAL (a.a + (10 * b.a) + (100 * c.a)) DAY AS Days
FROM (SELECT 0 AS a 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) AS a
CROSS JOIN (SELECT 0 AS a 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) AS b
CROSS JOIN (SELECT 0 AS a 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) AS c
) a
WHERE a.Days >= curdate() - INTERVAL 30 DAY) b
LEFT JOIN your_table
ON date = b.Days
ORDER BY b.Days;
これを分析してみましょう。
SELECT
IF(score IS NULL, 0, score) AS score,
b.Days AS date
Ifは、スコアのない日を検出して0に設定します。b.Daysは、現在の日付から取得するように選択した構成済みの日数で、最大1000日です。
(SELECT a.Days
FROM (
SELECT curdate() - INTERVAL (a.a + (10 * b.a) + (100 * c.a)) DAY AS Days
FROM (SELECT 0 AS a 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) AS a
CROSS JOIN (SELECT 0 AS a 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) AS b
CROSS JOIN (SELECT 0 AS a 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) AS c
) a
WHERE a.Days >= curdate() - INTERVAL 30 DAY) b
このサブクエリは、stackoverflowで見たものです。現在の日付から過去1000日間のリストを効率的に生成します。末尾のWHERE句の間隔(現在は30)によって、返される日が決まります。最大値は1000です。このクエリは、数百年分の日付を返すように簡単に変更できますが、ほとんどの場合、1000で十分です。
LEFT JOIN your_table
ON date = b.Days
ORDER BY b.Days;
これは、スコアを含むテーブルをそこに取り込む部分です。日付ジェネレータークエリから選択した日付範囲と比較して、必要に応じて0を入力できるようにします(スコアはLEFT JOIN
であるため、最初はNULL
に設定されます。これは選択ステートメント)。また、日付で注文します。これは好みですが、スコアで並べることもできます。
ORDER BY
の前に、編集で言及したユーザー情報についてテーブルに簡単に参加して、その最後の要件を追加できます。
このバージョンのクエリが誰かを助けることを願っています。読んでくれてありがとう。
マイケル・コナードの答えは素晴らしいですが、15分ごとに常に開始する必要がある15分の間隔が必要でした:
SELECT a.Days
FROM (
SELECT FROM_UNIXTIME( FLOOR( UNIX_TIMESTAMP() / (15 * 60) ) * (15 * 60)) - INTERVAL 15 * (a.a + (10 * b.a) + (100 * c.a)) MINUTE AS Days
FROM (SELECT 0 AS a 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) AS a
CROSS JOIN (SELECT 0 AS a 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) AS b
CROSS JOIN (SELECT 0 AS a 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) AS c
) a
WHERE a.Days >= curdate() - INTERVAL 30 DAY
これにより、現在の時刻が前のラウンドの15分に設定されます。
FROM_UNIXTIME( FLOOR( UNIX_TIMESTAMP() / (15 * 60) ) * (15 * 60))
そして、これは15分のステップで時間を削除します:
- INTERVAL 15 * (a.a + (10 * b.a) + (100 * c.a)) MINUTE
もっと簡単な方法があれば、教えてください。