PostgreSQL
 sql >> Teknologi Basis Data >  >> RDS >> PostgreSQL

Jumlah Total Rekaman per Minggu

Pendekatan sederhana adalah menyelesaikan ini dengan CROSS JOIN seperti yang ditunjukkan oleh @jpw. Namun, ada beberapa masalah yang tersembunyi :

  1. Kinerja dari CROSS JOIN unconditional tanpa syarat memburuk dengan cepat dengan bertambahnya jumlah baris. Jumlah total baris dikalikan dengan jumlah minggu yang Anda uji, sebelum tabel turunan besar ini dapat diproses dalam agregasi. Indeks tidak dapat membantu.

  2. Memulai minggu dengan 1 Januari menyebabkan inkonsistensi. minggu ISO mungkin bisa menjadi alternatif. Lihat di bawah.

Semua kueri berikut banyak menggunakan indeks pada exam_date . Pastikan untuk memilikinya.

Hanya bergabung dengan baris yang relevan

Seharusnya jauh lebih cepat :

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;

Ini dengan minggu mulai dari 1 Januari seperti di asli Anda. Seperti yang dikomentari, ini menghasilkan beberapa inkonsistensi:Minggu dimulai pada hari yang berbeda setiap tahun dan karena kami memotong pada akhir tahun, minggu terakhir tahun ini hanya terdiri dari 1 atau 2 hari (tahun kabisat).

Sama dengan minggu ISO

Bergantung pada persyaratan, pertimbangkan minggu ISO sebagai gantinya, yang dimulai pada hari Senin dan selalu berlangsung selama 7 hari. Tapi mereka melintasi perbatasan antara tahun. Per dokumentasi di EXTRACT() :

Kueri di atas ditulis ulang dengan minggu 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;

4 Januari selalu dalam minggu ISO pertama tahun ini. Jadi ekspresi ini mendapatkan tanggal Senin dari minggu ISO pertama tahun tertentu:

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

Sederhanakan dengan EXTRACT()

Karena minggu ISO bertepatan dengan angka minggu yang dikembalikan oleh EXTRACT() , kita dapat menyederhanakan kueri. Pertama, bentuk singkat dan sederhana:

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);

Kueri yang dioptimalkan

Hal yang sama dengan lebih banyak detail dan dioptimalkan untuk kinerja

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);

Ini harus sangat cepat dengan dukungan indeks dan dapat dengan mudah disesuaikan dengan interval pilihan. JOIN LATERAL implisit untuk generate_series() dalam kueri terakhir membutuhkan Postgres 9.3 .

SQL Fiddle.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Bagaimana cara mengubah kepemilikan beberapa tabel di dalam database dari postgres ke pengguna lain?

  2. Ambil catatan N pertama dari array JSON dengan kueri Postgresql

  3. Rails:Izin Postgres ditolak untuk membuat database di rake db:create:all

  4. Menghubungkan ke instance lokal PostgreSql dengan JDBC

  5. Mengurutkan nilai kolom yang berbeda dengan (nilai pertama) kolom lain dalam fungsi agregat