मैं प्रत्येक परिवर्तन को एक दृश्य में विघटित करते हुए, अपने समाधान को क्रमिक रूप से विकसित करने जा रहा हूं। यह दोनों यह समझाने में मदद करता है कि क्या किया जा रहा है, और डिबगिंग और परीक्षण में मदद करता है। यह अनिवार्य रूप से डेटाबेस प्रश्नों के लिए कार्यात्मक अपघटन के सिद्धांत को लागू कर रहा है।
मैं इसे Oracle एक्सटेंशन का उपयोग किए बिना भी करने जा रहा हूं, SQL के साथ जिसे किसी भी आधुनिक RBDMS पर चलाना चाहिए। तो नो कीप, ओवर, पार्टीशन, बस सबक्वायरी और ग्रुप बाय। (यदि यह आपके आरडीबीएमएस पर काम नहीं करता है तो मुझे टिप्पणियों में सूचित करें।)
सबसे पहले, तालिका, जिसे मैं रचनात्मक नहीं हूं, मैं माह_वैल्यू को कॉल करूंगा। चूंकि आईडी वास्तव में एक अद्वितीय आईडी नहीं है, इसलिए मैं इसे "ईद" कहूंगा। अन्य कॉलम "m"onth, "y"ear, और "v"alue:
. हैंcreate table month_value(
eid int not null, m int, y int, v int );
डेटा डालने के बाद, दो ईद के लिए, मेरे पास है:
> 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)
इसके बाद, हमारे पास एक इकाई है, महीना, जिसे दो चर के रूप में दर्शाया गया है। यह वास्तव में एक कॉलम होना चाहिए (या तो एक तारीख या एक डेटाटाइम, या शायद तारीखों की एक तालिका के लिए एक विदेशी कुंजी भी), इसलिए हम इसे एक कॉलम बना देंगे। हम इसे एक रैखिक परिवर्तन के रूप में करेंगे, जैसे कि यह (y, m) के समान है, और ऐसा है कि किसी भी (y,m) टपल के लिए एक और केवल मान है, और सभी मान लगातार हैं:
> create view cm_abs_month as
select *, y * 12 + m as am from month_value;
यह हमें देता है:
> 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)
अब हम एक सहसंबद्ध सबक्वेरी में सेल्फ-जॉइन का उपयोग करेंगे, प्रत्येक पंक्ति के लिए, सबसे पहला उत्तराधिकारी महीना जिसमें मूल्य बदलता है। हम इस दृश्य को हमारे द्वारा बनाए गए पिछले दृश्य पर आधारित करेंगे:
> 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 अब पहले (सबसे पुराने) महीने (वर्तमान पंक्ति के महीने के बाद) का "पूर्ण महीना" है जिसमें मान, v, बदलता है। जहां कोई बाद का महीना नहीं है, उस ईद के लिए, तालिका में यह शून्य है।
चूँकि last_am सभी महीनों के लिए समान है, v में परिवर्तन (जो last_am पर होता है) के लिए, हम last_am और v (और निश्चित रूप से ईद) पर समूह कर सकते हैं, और किसी भी समूह में, न्यूनतम (am) निरपेक्ष है पहला . का महीना लगातार महीने जिसका वह मान था:
> 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)
अब यह वह परिणाम सेट है जो हम चाहते हैं, यही कारण है कि इस दृश्य को cm_result_data कहा जाता है। निरपेक्ष महीने पहले (y,m) टुपल्स में बदलने के लिए बस कुछ कमी है।
ऐसा करने के लिए, हम केवल महीने_वैल्यू तालिका में शामिल होंगे।
केवल दो समस्याएं हैं:1) हम महीने पहले . चाहते हैं हमारे आउटपुट में last_am, और 2) हमारे पास नल हैं जहां हमारे डेटा में अगला महीना नहीं है; ओपी के विनिर्देशों को पूरा करने के लिए, वे एक महीने की सीमा होनी चाहिए।
संपादित करें:ये वास्तव में एक महीने से अधिक लंबी अवधि के हो सकते हैं, लेकिन हर मामले में उनका मतलब है कि हमें ईद के लिए नवीनतम महीना खोजने की जरूरत है, जो है:
(select max(am) from cm_abs_month d where d.eid = a.eid )
चूंकि विचार समस्या को विघटित करते हैं, इसलिए हम इस "एंड कैप" महीने पहले एक और दृश्य जोड़कर जोड़ सकते थे, लेकिन मैं इसे अभी सम्मिलित करूंगा। कौन सा सबसे कुशल होगा यह इस बात पर निर्भर करता है कि आपका आरडीबीएमएस कैसे प्रश्नों को अनुकूलित करता है।
महीने पहले प्राप्त करने के लिए, हम शामिल होंगे (cm_result_data.last_am - 1 =cm_abs_month.am)
जहां भी हमारे पास शून्य है, ओपी चाहता है कि "से" महीना "से" महीने जैसा ही हो, इसलिए हम उस पर केवल कोलेस का उपयोग करेंगे:कोलेस (last_am, am)। चूंकि last किसी भी नल को समाप्त करता है, हमारे जॉइन को बाहरी जॉइन होने की आवश्यकता नहीं है।
> 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 |
+-----+------+------+------+------+------+
वापस जुड़ने से हमें वह आउटपुट मिलता है जो ओपी चाहता है।
ऐसा नहीं है कि हमें वापस जुड़ना होगा। जैसा कि होता है, हमारा निरपेक्ष_माह कार्य द्वि-दिशात्मक है, इसलिए हम केवल वर्ष और उससे ऑफसेट महीने की पुनर्गणना कर सकते हैं।
सबसे पहले, "समाप्ति सीमा" माह जोड़ने पर ध्यान दें:
> 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;
और अब हमें डेटा मिलता है, ओपी के अनुसार स्वरूपित:
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 |
+-----+------+------+------+------+------+
और वह डेटा है जो ओपी चाहता है। सभी SQL में जो किसी भी RDBMS पर चलना चाहिए, और सरल, समझने में आसान और परीक्षण करने में आसान दृश्यों में विघटित हो जाता है।
क्या फिर से जुड़ना या पुनर्गणना करना बेहतर है? मैं इसे (यह एक ट्रिकी प्रश्न है) पाठक पर छोड़ दूँगा।
(यदि आपका RDBMS व्यू में ग्रुप बाय की अनुमति नहीं देता है, तो आपको पहले और फिर ग्रुप, या ग्रुप में शामिल होना होगा और फिर सहसंबद्ध सबक्वेरी के साथ महीने और साल को खींचना होगा। इसे पाठक के लिए एक अभ्यास के रूप में छोड़ दिया जाता है।)पी>
जोनाथन लेफ़लर टिप्पणियों में पूछते हैं,
<ब्लॉककोट>यदि डेटा में अंतराल है तो आपकी क्वेरी का क्या होगा (मान लें कि मान 80 के साथ 2007-12 के लिए प्रविष्टि है, और 2007-10 के लिए दूसरी है, लेकिन 2007-11 के लिए एक नहीं है? प्रश्न स्पष्ट नहीं है कि वहां क्या होना चाहिए।
ठीक है, आप बिल्कुल सही हैं, ओपी निर्दिष्ट नहीं करता है। शायद एक (बिना उल्लेख के) पूर्व शर्त है कि कोई अंतराल नहीं है। किसी आवश्यकता के अभाव में, हमें किसी ऐसी चीज़ के इर्द-गिर्द कोड करने का प्रयास नहीं करना चाहिए जो शायद नहीं है। लेकिन, तथ्य यह है कि अंतराल "वापस शामिल होने" की रणनीति को विफल कर देता है; उन शर्तों के तहत "पुनर्गणना" रणनीति विफल नहीं होती है। मैं और अधिक कहूंगा, लेकिन यह उस ट्रिक प्रश्न में ट्रिक को प्रकट करेगा जिसका मैंने ऊपर उल्लेख किया था।