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 :
-
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 đỡ. -
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 .