Tôi sẽ phát triển giải pháp của mình theo từng bước, phân tách từng chuyển đổi thành một chế độ xem. Điều này vừa giúp giải thích những gì đang được thực hiện, vừa giúp gỡ lỗi và kiểm tra. Về cơ bản, nó áp dụng nguyên tắc phân rã chức năng cho các truy vấn cơ sở dữ liệu.
Tôi cũng sẽ làm điều đó mà không sử dụng phần mở rộng Oracle, với SQL phải chạy trên bất kỳ RBDMS hiện đại nào. Vì vậy, không giữ lại, vượt qua, phân vùng, chỉ truy vấn con và nhóm bys. (Thông báo cho tôi trong phần nhận xét nếu nó không hoạt động trên RDBMS của bạn.)
Đầu tiên, bảng, vì tôi không có suy nghĩ, tôi sẽ gọi là month_value. Vì id thực sự không phải là id duy nhất, nên tôi sẽ gọi nó là "eid". Các cột khác là "m" onth, "y" ear và "v" alue:
create table month_value(
eid int not null, m int, y int, v int );
Sau khi chèn dữ liệu, đối với hai eids, tôi có:
> select * from month_value;
+-----+------+------+------+
| eid | m | y | v |
+-----+------+------+------+
| 100 | 1 | 2008 | 80 |
| 100 | 2 | 2008 | 80 |
| 100 | 3 | 2008 | 90 |
| 100 | 4 | 2008 | 80 |
| 200 | 1 | 2008 | 80 |
| 200 | 2 | 2008 | 80 |
| 200 | 3 | 2008 | 90 |
| 200 | 4 | 2008 | 80 |
+-----+------+------+------+
8 rows in set (0.00 sec)
Tiếp theo, chúng ta có một thực thể, tháng, được biểu thị dưới dạng hai biến. Đó thực sự phải là một cột (ngày tháng hoặc ngày giờ, hoặc thậm chí có thể là một khóa ngoại cho bảng ngày tháng), vì vậy chúng tôi sẽ làm cho nó thành một cột. Chúng tôi sẽ thực hiện điều đó dưới dạng một phép biến đổi tuyến tính, sao cho nó sắp xếp giống như (y, m) và sao cho bất kỳ bộ (y, m) nào đều có một giá trị duy nhất và tất cả các giá trị đều liên tiếp:
> create view cm_abs_month as
select *, y * 12 + m as am from month_value;
Điều đó mang lại cho chúng tôi:
> select * from cm_abs_month;
+-----+------+------+------+-------+
| eid | m | y | v | am |
+-----+------+------+------+-------+
| 100 | 1 | 2008 | 80 | 24097 |
| 100 | 2 | 2008 | 80 | 24098 |
| 100 | 3 | 2008 | 90 | 24099 |
| 100 | 4 | 2008 | 80 | 24100 |
| 200 | 1 | 2008 | 80 | 24097 |
| 200 | 2 | 2008 | 80 | 24098 |
| 200 | 3 | 2008 | 90 | 24099 |
| 200 | 4 | 2008 | 80 | 24100 |
+-----+------+------+------+-------+
8 rows in set (0.00 sec)
Bây giờ, chúng ta sẽ sử dụng phép tự nối trong một truy vấn con tương quan để tìm, cho mỗi hàng, tháng kế tiếp sớm nhất mà giá trị thay đổi. Chúng tôi sẽ dựa trên chế độ xem này dựa trên chế độ xem trước đó mà chúng tôi đã tạo:
> create view cm_last_am as
select a.*,
( select min(b.am) from cm_abs_month b
where b.eid = a.eid and b.am > a.am and b.v <> a.v)
as last_am
from cm_abs_month a;
> select * from cm_last_am;
+-----+------+------+------+-------+---------+
| eid | m | y | v | am | last_am |
+-----+------+------+------+-------+---------+
| 100 | 1 | 2008 | 80 | 24097 | 24099 |
| 100 | 2 | 2008 | 80 | 24098 | 24099 |
| 100 | 3 | 2008 | 90 | 24099 | 24100 |
| 100 | 4 | 2008 | 80 | 24100 | NULL |
| 200 | 1 | 2008 | 80 | 24097 | 24099 |
| 200 | 2 | 2008 | 80 | 24098 | 24099 |
| 200 | 3 | 2008 | 90 | 24099 | 24100 |
| 200 | 4 | 2008 | 80 | 24100 | NULL |
+-----+------+------+------+-------+---------+
8 rows in set (0.01 sec)
last_am bây giờ là "tháng tuyệt đối" của tháng đầu tiên (sớm nhất) (sau tháng của hàng hiện tại) trong đó giá trị, v, thay đổi. Nó không có giá trị mà không có tháng sau, cho eid đó, trong bảng.
Vì last_am giống nhau trong tất cả các tháng dẫn đến sự thay đổi trong v (xảy ra tại last_am), chúng ta có thể nhóm trên last_am và v (tất nhiên và eid), và trong bất kỳ nhóm nào, min (am) là giá trị tuyệt đối tháng của đầu tiên tháng liên tiếp có giá trị đó:
> create view cm_result_data as
select eid, min(am) as am , last_am, v
from cm_last_am group by eid, last_am, v;
> select * from cm_result_data;
+-----+-------+---------+------+
| eid | am | last_am | v |
+-----+-------+---------+------+
| 100 | 24100 | NULL | 80 |
| 100 | 24097 | 24099 | 80 |
| 100 | 24099 | 24100 | 90 |
| 200 | 24100 | NULL | 80 |
| 200 | 24097 | 24099 | 80 |
| 200 | 24099 | 24100 | 90 |
+-----+-------+---------+------+
6 rows in set (0.00 sec)
Bây giờ đây là tập kết quả chúng tôi muốn, đó là lý do tại sao khung nhìn này được gọi là cm_result_data. Tất cả những gì còn thiếu là thứ để biến đổi các tháng tuyệt đối trở lại (y, m) bộ giá trị.
Để làm điều đó, chúng ta sẽ chỉ tham gia vào bảng month_value.
Chỉ có hai vấn đề:1) chúng tôi muốn tháng trước last_am trong đầu ra của chúng tôi, và2) chúng tôi có null khi không có tháng tiếp theo trong dữ liệu của chúng tôi; để đáp ứng thông số kỹ thuật của OP, đó phải là phạm vi tháng duy nhất.
CHỈNH SỬA:Đây thực sự có thể là phạm vi dài hơn một tháng, nhưng trong mọi trường hợp, chúng có nghĩa là chúng tôi cần tìm tháng mới nhất cho eid, đó là:
(select max(am) from cm_abs_month d where d.eid = a.eid )
Bởi vì các chế độ xem giải quyết vấn đề, chúng tôi có thể thêm vào "giới hạn kết thúc" tháng này sớm hơn, bằng cách thêm một chế độ xem khác, nhưng tôi sẽ chỉ chèn cái này vào liên kết. Điều nào sẽ hiệu quả nhất tùy thuộc vào cách RDBMS của bạn tối ưu hóa các truy vấn.
Trước tháng trước, chúng ta sẽ tham gia (cm_result_data.last_am - 1 =cm_abs_month.am)
Bất cứ nơi nào chúng tôi có giá trị rỗng, OP muốn tháng "đến" giống với tháng "từ", vì vậy chúng tôi sẽ chỉ sử dụng liên kết trên đó:thanesce (last_am, am). Vì lần cuối cùng loại bỏ mọi null nên các phép nối của chúng ta không cần phải là các phép nối bên ngoài.
> select a.eid, b.m, b.y, c.m, c.y, a.v
from cm_result_data a
join cm_abs_month b
on ( a.eid = b.eid and a.am = b.am)
join cm_abs_month c
on ( a.eid = c.eid and
coalesce( a.last_am - 1,
(select max(am) from cm_abs_month d where d.eid = a.eid )
) = c.am)
order by 1, 3, 2, 5, 4;
+-----+------+------+------+------+------+
| eid | m | y | m | y | v |
+-----+------+------+------+------+------+
| 100 | 1 | 2008 | 2 | 2008 | 80 |
| 100 | 3 | 2008 | 3 | 2008 | 90 |
| 100 | 4 | 2008 | 4 | 2008 | 80 |
| 200 | 1 | 2008 | 2 | 2008 | 80 |
| 200 | 3 | 2008 | 3 | 2008 | 90 |
| 200 | 4 | 2008 | 4 | 2008 | 80 |
+-----+------+------+------+------+------+
Bằng cách kết hợp trở lại, chúng tôi nhận được kết quả đầu ra mà OP mong muốn.
Không phải là chúng ta phải tham gia trở lại. Khi nó xảy ra, hàm tuyệt đối của chúng tôi là hai chiều, vì vậy chúng tôi có thể chỉ cần tính toán lại năm và bù trừ tháng từ nó.
Trước tiên, chúng ta hãy quan tâm đến việc thêm "giới hạn kết thúc" tháng:
> create or replace view cm_capped_result as
select eid, am,
coalesce(
last_am - 1,
(select max(b.am) from cm_abs_month b where b.eid = a.eid)
) as last_am, v
from cm_result_data a;
Và bây giờ chúng tôi nhận được dữ liệu, được định dạng theo OP:
select eid,
( (am - 1) % 12 ) + 1 as sm,
floor( ( am - 1 ) / 12 ) as sy,
( (last_am - 1) % 12 ) + 1 as em,
floor( ( last_am - 1 ) / 12 ) as ey, v
from cm_capped_result
order by 1, 3, 2, 5, 4;
+-----+------+------+------+------+------+
| eid | sm | sy | em | ey | v |
+-----+------+------+------+------+------+
| 100 | 1 | 2008 | 2 | 2008 | 80 |
| 100 | 3 | 2008 | 3 | 2008 | 90 |
| 100 | 4 | 2008 | 4 | 2008 | 80 |
| 200 | 1 | 2008 | 2 | 2008 | 80 |
| 200 | 3 | 2008 | 3 | 2008 | 90 |
| 200 | 4 | 2008 | 4 | 2008 | 80 |
+-----+------+------+------+------+------+
Và có dữ liệu OP muốn. Tất cả trong SQL sẽ chạy trên bất kỳ RDBMS nào và được phân tách thành các dạng xem đơn giản, dễ hiểu và dễ kiểm tra.
Tốt hơn là nên tham gia lại hay tính toán lại? Tôi sẽ để đó (đó là một câu hỏi mẹo) cho người đọc.
(Nếu RDBMS của bạn không cho phép chia theo nhóm trong các chế độ xem, trước tiên bạn sẽ phải tham gia nhóm, hoặc nhóm và sau đó kéo theo tháng và năm với các truy vấn con tương quan. Đây là bài tập cho người đọc.)
Jonathan Leffler hỏi trong phần bình luận,
Điều gì sẽ xảy ra với truy vấn của bạn nếu có khoảng trống trong dữ liệu (giả sử có một bản khác cho 2007-12 với giá trị 80 và một cái khác cho 2007-10, nhưng không phải một cho 2007-11? Câu hỏi không rõ ràng là điều gì sẽ xảy ra ở đó.
Chà, bạn nói chính xác, OP không chỉ rõ. Có lẽ có một điều kiện trước (chưa được đề cập) là không có khoảng trống. Trong trường hợp không có yêu cầu, chúng ta không nên cố gắng viết mã xung quanh thứ gì đó có thể không có ở đó. Nhưng, thực tế là, những lỗ hổng khiến chiến lược “gia nhập trở lại” thất bại; chiến lược "tính toán lại" không thất bại trong những điều kiện đó. Tôi muốn nói nhiều hơn, nhưng điều đó sẽ tiết lộ mẹo trong câu hỏi mẹo mà tôi đã ám chỉ ở trên.