6

ここに私のSQLがあります:

SELECT 
  COUNT(id),
  CONCAT(YEAR(created_at), '-', MONTH(created_at), '-', DAY(created_at))
FROM my_table
GROUP BY YEAR(created_at), MONTH(created_at), DAY(created_at)

IDが作成されていない日でも行が表示されるようにしたい。現在、活動がなかった日の日付がたくさんありません。

このクエリを変更してそれを行う方法について何か考えはありますか?

4

4 に答える 4

9

SQL は、データベースにないデータを返すのが苦手なことで有名です。日付のギャップの開始値と終了値を見つけることはできますが、すべての日付を取得するのは困難です。

解決策は、日付ごとに 1 つのレコードを含むカレンダー テーブルを作成し、それをクエリに OUTER JOIN することです。

以下は、created_at が DATE 型であると仮定した例です。

SELECT calendar_date, COUNT(`id`)
FROM calendar LEFT OUTER JOIN my_table ON calendar.calendar_date = my_table.created_at
GROUP BY calendar_date

(created_at は実際には DATETIME であると推測しているため、テーブルを結合するにはもう少し体操を行う必要があります)。

于 2012-04-05T19:23:33.187 に答える
8

一般的なアイデア

MySQL でデータを生成するには、主に 2 つの方法があります。1 つはクエリの実行時にその場でデータを生成することで、もう 1 つはデータベースにデータを保持し、必要に応じて使用することです。もちろん、クエリを頻繁に実行する場合は、最初のクエリよりも 2 番目のクエリの方が高速です。ただし、2 つ目はデータベースにテーブルを必要とし、その目的は欠落データを生成することだけです。また、そのテーブルを作成するのに十分な権限を持っている必要があります。

動的データ生成

このアプローチには、UNIONs を作成して、実際のテーブルを結合するために使用できる偽のテーブルを生成することが含まれます。ひどい繰り返しのクエリは次のとおりです。

select aDate from (
  select @maxDate - interval (a.a+(10*b.a)+(100*c.a)+(1000*d.a)) day aDate 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) a, /*10 day range*/
  (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) b, /*100 day range*/
  (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) c, /*1000 day range*/
  (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) d, /*10000 day range*/
  (select @minDate := '2001-01-01', @maxDate := '2002-02-02') e
) f
where aDate between @minDate and @maxDate

とにかく、見た目よりも簡単です。派生テーブルのデカルト積を10数値で作成するため、結果には、クエリ内の派生テーブルの量である10^X行が含まれます。Xこの例では10000日の範囲があるため、何年にもわたる期間を表すことができます27。さらに必要な場合はUNION、クエリに別の値を追加して間隔を更新します。それほど多くの値が必要ない場合はUNION、派生テーブルから s または個々の値を削除できます。明確にするために、WHERE句 on @minDateand@maxDate変数を使用してフィルターを適用することにより、日付期間を微調整できます (ただし、デカルト積で作成したものよりも長い期間を使用しないでください)。

静的データの生成

このソリューションでは、データベースにテーブルを生成する必要があります。アプローチは前のものと似ています。最初にそのテーブルにデータを挿入する必要があります。必要な最大範囲は から までの整数の範囲です1。繰り返しますが、不明な場合は、値を挿入するだけで、何年にもわたる日の範囲を作成できます。したがって、整数シーケンスを取得したら、次のように日付範囲に変換できます。XX100000273

select '2012-01-01' + interval value - 1 day aDay from seq
having aDay <= '2012-01-05'

seqという名前の列を持つ名前のテーブルを想定していますvalue。上が開始日、下が終了日です。

これを有用なものに変える

さて、これで日付期間が生成されましたが、データを照会して欠損値を実際の値として表示する方法がまだありません0。これがleft join救助に来るところです。すべてが同じページにあることを確認するために、aleft joinは an に似ていますinner joinが、1 つだけ違いがあります。右側のテーブルに一致するレコードがあるかどうかに関係なく、結合の左側のテーブルからすべてのレコードが保持されます。 . つまり、inner joinは結合で一致しない行をすべて削除し、 は左側のテーブルの行left joinを保持し、右側のテーブルに一致するレコードがない左側のレコードについては、left joinその「スペース」を埋めます。null値で。

そのため、ドメイン テーブル (「欠落している」データを含むテーブル) を新しく生成されたテーブルに結合し、後者を結合の左側に、前者を右側に配置して、すべての要素がその存在に関係なく考慮されるようにする必要があります。ドメインテーブルで。

たとえば、domainTableフィールドを含むテーブルがあり、1 日あたりの最初の数日間ID, birthDateのすべての数を確認したい場合、および数がその値を表示する場合は、次のクエリを実行できます。birthDate520120

select allDays.aDay, count(dt.id) from (
  select '2012-01-01' + interval value - 1 day aDay from seq
  having aDay <= '2012-01-05'
) allDays
left join domainTable dt on allDays.aDay = dt.birthDate
group by allDays.aDay

これにより、必要なすべての日を含む派生テーブルが生成され (静的データ生成を使用していることに注意してください) left join、ドメイン テーブルに対して a が実行されるため、ドメイン テーブルに一致する値があるかどうかに関係なく、すべての日が表示されます。また、値はカウントされないため、値countを持つフィールドで実行する必要があることに注意してください。null

注意事項

1)クエリを使用して、コードに小さな変更を加えて他の間隔(月、年)をクエリできます

2) クエリできる日付とドメイン テーブルの値をハードコーディングする代わりに、次のminようmaxにします。

select (select min(aDate) from domainTable) + interval value - 1 day aDay
from seq
having aDay <= (select max(aDate) from domainTable)

これにより、必要以上のレコードが生成されることを回避できます。

実際にあなたの質問に答える

自分がやりたいことをする方法をすでに理解しているはずだと思います。とにかく、他の人もそれらの恩恵を受けることができるように、ここに手順があります. まず、整数テーブルを作成します。次に、次のクエリを実行します。

select allDays.aDay, count(mt.id) aCount from (
    select (select date(min(created_at)) from my_table) + interval value - 1 day aDay
    from seq s
    having aDay <= (select date(max(created_at)) from my_table)
) allDays
left join my_table mt on allDays.aDay = date(mt.created_at)
group by allDays.aDay

created_atは日時だと思うので、そのように連結しています。ただし、これは MySQL がネイティブに日付を格納する方法であるため、日付フィールドでグループ化しcreated_at、実際のdateデータ型にキャストしています。このfiddleを使用して遊ぶことができます。

そして、データを動的に生成するソリューションは次のとおりです。

select allDays.aDay, count(mt.id) aCount from (
  select @maxDate - interval a.a day aDay 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) a, /*10 day range*/
  (select @minDate := (select date(min(created_at)) from my_table),
          @maxDate := (select date(max(created_at)) from my_table)) e
   where @maxDate - interval a.a day between @minDate and @maxDate
) allDays
left join my_table mt on allDays.aDay = date(mt.created_at)
group by allDays.aDay

ご覧のとおり、クエリの骨組みは前のものと同じです。唯一の変更点は、派生テーブルのallDays生成方法です。これで、派生テーブルの生成方法も以前に追加したものとは少し異なります。これは、例の filddle では -day10範囲のみが必要だったためです。1000ご覧のとおり、日の範囲を追加するよりも読みやすいです。これは、動的ソリューションのフィドルであり、それで遊ぶこともできます。

お役に立てれば!

于 2012-04-10T00:28:06.103 に答える