PostgreSQL
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> PostgreSQL

Tổng số bản ghi mỗi tuần

Cách tiếp cận đơn giản sẽ là giải quyết vấn đề này với CROSS JOIN như được minh họa bởi @jpw. Tuy nhiên, có một số vấn đề ẩn :

  1. Hiệu suất của một CROSS JOIN vô điều kiện xấu đi nhanh chóng với số lượng hàng ngày càng tăng. Tổng số hàng được nhân với số tuần bạn đang thử nghiệm, trước khi bảng dẫn xuất khổng lồ này có thể được xử lý trong tập hợp. Chỉ mục không thể giúp đỡ.

  2. Các tuần bắt đầu từ ngày 1 tháng 1 dẫn đến sự mâu thuẫn. Tuần ISO có thể là một sự thay thế. Xem bên dưới.

Tất cả các truy vấn sau đều sử dụng nhiều chỉ mục trên exam_date . Hãy chắc chắn có một cái.

Chỉ tham gia vào các hàng có liên quan

Sẽ nhanh hơn nhiều :

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;

Điều này là với các tuần bắt đầu từ ngày 1 tháng 1 giống như trong bản gốc của bạn. Như đã nhận xét, điều này tạo ra một vài điểm mâu thuẫn:Các tuần bắt đầu vào một ngày khác nhau mỗi năm và vì chúng ta kết thúc vào cuối năm nên tuần cuối cùng của năm chỉ bao gồm 1 hoặc 2 ngày (năm nhuận).

Tương tự với các tuần ISO

Tùy thuộc vào yêu cầu, hãy xem xét tuần ISO thay vào đó, bắt đầu vào Thứ Hai và luôn kéo dài 7 ngày. Nhưng họ đã vượt qua biên giới giữa các năm. Theo tài liệu trên EXTRACT () :

Truy vấn trên được viết lại với ISO tuần:

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;

Ngày 4 tháng 1 luôn nằm trong tuần lễ ISO đầu tiên của năm. Vì vậy, biểu thức này nhận ngày thứ Hai của tuần ISO đầu tiên của năm nhất định:

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

Đơn giản hóa với EXTRACT ()

Vì tuần ISO trùng với số tuần được trả về bởi EXTRACT () , chúng tôi có thể đơn giản hóa truy vấn. Đầu tiên, một biểu mẫu ngắn gọn và đơn giản:

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

Truy vấn được tối ưu hóa

Tương tự với nhiều chi tiết hơn và được tối ưu hóa cho hiệu suất

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

Điều này sẽ rất nhanh với hỗ trợ chỉ mục và có thể dễ dàng được điều chỉnh theo các khoảng thời gian lựa chọn. cho create_series () trong truy vấn cuối cùng yêu cầu 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. Cách chèn dữ liệu CSV vào cơ sở dữ liệu PostgreSQL (cơ sở dữ liệu từ xa)

  2. Mảng PostgreSQL của các phần tử mà mỗi phần tử là một khóa ngoại

  3. Lưu trữ kết quả truy vấn PostgreSQL vào biến Shell hoặc PostgreSQL

  4. Postgres:Chuyển các kiểu tùy chỉnh từ Java sang hàm postgres

  5. cột users.id phải xuất hiện trong mệnh đề GROUP BY hoặc được sử dụng trong một hàm tổng hợp