sql >> データベース >  >> RDS >> PostgreSQL

週あたりのレコードの総数

    簡単なアプローチは、@jpwで示されているようなCROSSJOINを使用してこれを解決することです。ただし、いくつかの隠れた問題があります :

    1. パフォーマンス 無条件のCROSS JOINの 行数が増えるとすぐに劣化します。この巨大な派生テーブルを集計で処理する前に、行の総数にテストする週数を掛けます。インデックスは役に立ちません。

    2. 1月1日から数週間を開始すると、不整合が発生します。 ISO週間 代替案かもしれません。以下を参照してください。

    以下のクエリはすべて、exam_dateインデックスを多用します。 。必ず持ってください。

    関連する行にのみ結合する

    はるかに高速である必要があります

    SELECT d.day, d.thisyr
         , count(t.exam_date) AS lastyr
    FROM  (
       SELECT d.day::date, (d.day - '1 year'::interval)::date AS day0  -- for 2nd join
            , count(t.exam_date) AS thisyr
       FROM   generate_series('2013-01-01'::date
                            , '2013-01-31'::date  -- last week overlaps with Feb.
                            , '7 days'::interval) d(day)  -- returns timestamp
       LEFT   JOIN tbl t ON t.exam_date >= d.day::date
                        AND t.exam_date <  d.day::date + 7
       GROUP  BY d.day
       ) d
    LEFT   JOIN tbl t ON t.exam_date >= d.day0      -- repeat with last year
                     AND t.exam_date <  d.day0 + 7
    GROUP  BY d.day, d.thisyr
    ORDER  BY d.day;
    

    これはあなたのオリジナルのように1月1日から始まる週です。コメントされているように、これにはいくつかの矛盾が生じます。週は毎年異なる日に始まり、年末に終了するため、年の最後の週は1日または2日(うるう年)で構成されます。

    ISO週と同じ

    要件に応じて、ISO週間を検討してください 代わりに、月曜日に開始し、常に7日間に渡ります。しかし、彼らは年の間に国境を越えます。 EXTRACT()

    上記のクエリはISO週で書き直されました:

    SELECT w AS isoweek
         , day::text  AS thisyr_monday, thisyr_ct
         , day0::text AS lastyr_monday, count(t.exam_date) AS lastyr_ct
    FROM  (
       SELECT w, day
            , date_trunc('week', '2012-01-04'::date)::date + 7 * w AS day0
            , count(t.exam_date) AS thisyr_ct
       FROM  (
          SELECT w
               , date_trunc('week', '2013-01-04'::date)::date + 7 * w AS day
          FROM   generate_series(0, 4) w
          ) d
       LEFT   JOIN tbl t ON t.exam_date >= d.day
                        AND t.exam_date <  d.day + 7
       GROUP  BY d.w, d.day
       ) d
    LEFT   JOIN tbl t ON t.exam_date >= d.day0     -- repeat with last year
                     AND t.exam_date <  d.day0 + 7
    GROUP  BY d.w, d.day, d.day0, d.thisyr_ct
    ORDER  BY d.w, d.day;
    

    1月4日は常にその年の最初のISO週です。したがって、この式は、指定された年の最初のISO週の月曜日の日付を取得します。

    date_trunc('week', '2012-01-04'::date)::date
    

    EXTRACT()

    ISOの週は、EXTRACT()によって返される週番号と一致するため 、クエリを簡略化できます。まず、短くて単純な形式:

    SELECT w AS isoweek
         , COALESCE(thisyr_ct, 0) AS thisyr_ct
         , COALESCE(lastyr_ct, 0) AS lastyr_ct
    FROM   generate_series(1, 5) w
    LEFT   JOIN (
       SELECT EXTRACT(week FROM exam_date)::int AS w, count(*) AS thisyr_ct
       FROM   tbl
       WHERE  EXTRACT(isoyear FROM exam_date)::int = 2013
       GROUP  BY 1
       ) t13  USING (w)
    LEFT   JOIN (
       SELECT EXTRACT(week FROM exam_date)::int AS w, count(*) AS lastyr_ct
       FROM   tbl
       WHERE  EXTRACT(isoyear FROM exam_date)::int = 2012
       GROUP  BY 1
       ) t12  USING (w);
    

    最適化されたクエリ

    詳細についても同じで、パフォーマンスが最適化されています

    WITH params AS (          -- enter parameters here, once 
       SELECT date_trunc('week', '2012-01-04'::date)::date AS last_start
            , date_trunc('week', '2013-01-04'::date)::date AS this_start
            , date_trunc('week', '2014-01-04'::date)::date AS next_start
            , 1 AS week_1
            , 5 AS week_n     -- show weeks 1 - 5
       )
    SELECT w.w AS isoweek
         , p.this_start + 7 * (w - 1) AS thisyr_monday
         , COALESCE(t13.ct, 0) AS thisyr_ct
         , p.last_start + 7 * (w - 1) AS lastyr_monday
         , COALESCE(t12.ct, 0) AS lastyr_ct
    FROM params p
       , generate_series(p.week_1, p.week_n) w(w)
    LEFT   JOIN (
       SELECT EXTRACT(week FROM t.exam_date)::int AS w, count(*) AS ct
       FROM   tbl t, params p
       WHERE  t.exam_date >= p.this_start      -- only relevant dates
       AND    t.exam_date <  p.this_start + 7 * (p.week_n - p.week_1 + 1)::int
    -- AND    t.exam_date <  p.next_start      -- don't cross over into next year
       GROUP  BY 1
       ) t13  USING (w)
    LEFT   JOIN (                              -- same for last year
       SELECT EXTRACT(week FROM t.exam_date)::int AS w, count(*) AS ct
       FROM   tbl t, params p
       WHERE  t.exam_date >= p.last_start
       AND    t.exam_date <  p.last_start + 7 * (p.week_n - p.week_1 + 1)::int
    -- AND    t.exam_date <  p.this_start
       GROUP  BY 1
       ) t12  USING (w);
    

    これはインデックスのサポートにより非常に高速であり、選択した間隔に簡単に適合させることができます。暗黙のJOIN LATERAL generate_series()の場合 最後のクエリでは、 Postgres 9.3が必要です 。

    SQLフィドル。



    1. 未処理の例外コンパイルエラー:ClassNotFoundException

    2. Postgresql。 CREATECAST'文字が'から'整数'に変化します

    3. plpgsql関数の動的ORDERBYおよびASC/DESC

    4. ユーザーがアクセスできないSQLデータベースを非表示にする方法