2015-01-08 9 views
9

मुझे एक प्रश्न बनाने में कुछ परेशानी हो रही है जो मेरी वस्तुओं को मासिक श्रेणी में समूहित करेगी जब भी वे एक महीने में हों या नहीं। मैं PostgreSQL का उपयोग कर रहा हूँ।मासिक अवधि समूहबद्ध करने के लिए एसक्यूएल क्वेरी

उदाहरण के लिए मैं इस रूप में डेटा की एक तालिका है:

Name Period(text) 
Ana  2010/09 
Ana  2010/10 
Ana  2010/11 
Ana  2010/12 
Ana  2011/01 
Ana  2011/02 
Peter 2009/05 
Peter 2009/06 
Peter 2009/07 
Peter 2009/08 
Peter 2009/12 
Peter 2010/01 
Peter 2010/02 
Peter 2010/03 
John 2009/05 
John 2009/06 
John 2009/09 
John 2009/11 
John 2009/12 

और मैं परिणाम क्वेरी इस होना चाहते हैं:

Name Start  End 
Ana  2010/09 2011/02 
Peter 2009/05 2009/08 
Peter 2009/12 2010/03 
John 2009/05 2009/06 
John 2009/09 2009/09 
John 2009/11 2009/12 

वहाँ किसी भी तरह से इस लक्ष्य को हासिल करने के लिए है?

+0

आपने क्या प्रयास किया है? कृपया एक अच्छा प्रश्न पूछें [http://stackoverflow.com/help/how-to-ask) और [न्यूनतम, पूर्ण और सत्यापन योग्य उदाहरण कैसे बनाएं] (http: // stackoverflow। com/मदद/mcve)। – adamdc78

उत्तर

7

यह एक एकत्रीकरण समस्या है, लेकिन एक मोड़ के साथ - आप प्रत्येक नाम के लिए आसन्न महीने के समूह निर्धारित की जरूरत है।

यह मानते हुए कि महीने किसी दिए गए नाम के लिए कभी भी एक से अधिक बार प्रकट नहीं होता है, तो आप प्रत्येक अवधि में "माह" संख्या निर्दिष्ट करके और अनुक्रमिक संख्या घटाकर ऐसा कर सकते हैं। मूल्य लगातार महीनों के लिए स्थिर रहेगा।

select name, min(period), max(period) 
from (select t.*, 
      (cast(left(period, 4) as int) * 12 + cast(right(period, 2) as int) - 
       row_number() over (partition by name order by period) 
      ) as grp 
     from names t 
    ) t 
group by grp, name; 

Here एक एसक्यूएल फिडल इसका वर्णन करता है।

नोट: डुप्लीकेट वास्तव में कोई समस्या नहीं है। आप row_number() के बजाय dense_rank() का उपयोग करेंगे।

+0

मुझे पता था कि यह लिखने के लिए एक बेहतर/छोटा तरीका था! मैंने 'row_number() - कुछ चीज की कोशिश की लेकिन" कुछ "व्यक्त करने का सही तरीका नहीं मिला। –

+0

ठंडा, क्या आपको लगता है कि प्रदर्शन के अनुसार यह रिकर्सिव से अधिक तेज होगा? –

+0

@RomanPekar। । । हाँ। रिकर्सिव सीटीई का प्रदर्शन आमतौर पर समकक्ष प्रश्नों से भी बदतर होता है जो उनका उपयोग नहीं करते हैं। –

6

अगर वहाँ एक आसान तरीका है मैं नहीं जानता कि (वहाँ शायद है) लेकिन मैं एक की अभी सोच भी नहीं सकते:

with parts as (
    select name, 
     to_date(replace(period,'/',''), 'yyyymm') as period 
    from names 
), flagged as (
    select name, 
     period, 
     case 
      when lag(period,1, (period - interval '1' month)::date) over (partition by name order by period) = (period - interval '1' month)::date then null 
      else 1 
     end as group_flag 
    from parts 
), grouped as (
    select flagged.*, 
     coalesce(sum(group_flag) over (partition by name order by period),0) as group_nr 
    from flagged 
) 
select name, min(period), max(period) 
from grouped 
group by name, group_nr 
order by name, min(period); 

पहले common table expression (parts) साधारण परिवर्तन एक में अवधि तारीख ताकि इसे अंकगणितीय अभिव्यक्ति में उपयोग किया जा सके।

दूसरा सीटीई (flagged) प्रत्येक पंक्ति के अंतराल (महीनों में) वर्तमान पंक्ति के बीच एक ध्वज निर्दिष्ट करता है और पिछला नहीं है।

तीसरा सीटीई तब उन झंडे को जमा करता है ताकि प्रत्येक पंक्तियों की लगातार संख्या के लिए एक अद्वितीय समूह संख्या निर्धारित की जा सके।

अंतिम चयन तब प्रत्येक समूह के लिए प्रारंभ और समाप्ति अवधि प्राप्त करता है। हालांकि, इस अवधि को मूल प्रारूप में बदलने के लिए परेशान नहीं था।

SQLFiddle उदाहरण है कि यह भी flagged CTE के मध्यवर्ती परिणाम दिखाता है:
http://sqlfiddle.com/#!15/8c0aa/2

+0

अच्छा काम लेकिन काफी नहीं, आपको साल के अंत में पीटर के लिए तीन प्रविष्टियां मिलेंगी। –

+0

अच्छा, मुझे लगता है कि यह गलत तरीके से काम कर सकता है क्योंकि 201001 - 1 <> 200 9 12।आपको वास्तविक तिथियों का उपयोग करना होगा और फिर यह काम करेगा :) –

+1

@RobertBain: हाँ, आप सही हैं! उचित अंकगणितीय नियम प्राप्त करने के लिए मुझे अवधि को वास्तविक तिथि में बदलना होगा। मेरा संपादन –

2

खैर आम तरीके करने के लिए इस पुनरावर्ती एसक्यूएल हो सकता है में से एक:

with recursive cte1 as (
    select 
     "Name" as name, 
     ("Period"||'/01')::date as period 
    from Table1 
), cte2 as (
    select 
     c.name, c.period as s, c.period as e 
    from cte1 as c 
    where not exists (select * from cte1 as t where t.name = c.name and t.period = c.period - interval '1 month') 

    union all 

    select 
     c.name, c.s as s, t.period 
    from cte2 as c 
     inner join cte1 as t on t.name = c.name and t.period = c.e + interval '1 month' 

) 
select 
    c.name, to_char(c.s, 'YYYY/MM') as "Start", to_char(max(c.e), 'YYYY/MM') as "End" 
from cte2 as c 
group by c.name, c.s 
order by 1, 2 

मैं के बारे में निश्चित नहीं हूँ इसका प्रदर्शन, आपको इसका परीक्षण करना होगा।

sql fiddle demo

संबंधित मुद्दे