2012-11-29 8 views
5

पर प्रत्येक बिंदु पर सबसे हालिया संस्करण ढूंढें, मैं दिनांक अंतराल के एक सेट के साथ काम कर रहा हूं जहां प्रत्येक अंतराल में संस्करण संख्या होती है और नए अंतराल अक्सर पुराने लोगों को ओवरलैप करते हैं, या यहां तक ​​कि उनके सबसेट भी हो। इस डेटा से मुझे अंतराल के एक नए सेट की गणना करने की आवश्यकता है जो प्रत्येक बिंदु पर सबसे हालिया संस्करण संख्या दिखाता है। क्या इस समस्या का एक सेट-आधारित समाधान है?ओवरलैपिंग के एक सेट में, संस्करण-क्रमांकित अंतराल,

Interval 1: 11111111111111111111111  
Interval 2:  2222222222    
Interval 3: 33333333333333    
Interval 4:      444444444 
Interval 5:     555555555 
Result : 11333333333333331155555555544 

यहाँ डेटा का एक नमूना के साथ मैं काम कर रहा हूँ है:

यहाँ एक उदाहरण दिया गया है

groupId startDate endDate  version 
-------- --------- ---------- ------ 
1   1/1/2010 1/1/2011 1 
1   10/1/2010 7/5/2011 2 
1   7/5/2011 8/13/2012 3 
1   8/13/2012 12/31/2012 6 
1   10/1/2012 11/1/2012 8 

... और वांछित उत्पादन:

groupId startDate endDate  version 
-------- --------- ---------- ------ 
1   1/1/2010 10/1/2010 1 
1   10/1/2010 7/5/2011 2 
1   7/5/2011 8/13/2012 3 
1   8/13/2011 10/1/2012 6 
1   10/1/2012 11/1/2012 8 << note how version 8 supersedes version 6 
1   11/1/2012 12/31/2012 6 << version 6 is split into two records 

मुझे इस समस्या का कोई अन्य उदाहरण नहीं मिला है, मेरा गुगल केवल उन प्रश्नों को बदलता है जो gaps and islands याकी पहचान करते हैं।

मुझे लगता है कि मेरे पास एक पुनरावृत्ति समाधान (SQL सर्वर 2008) है। यह परिणाम सेट में अंतराल के लिए एक अस्थायी तालिका से शुरू होता है और उस सीमा के लिए प्रारंभ और अंत बिंदु को परिभाषित करता है जिसे हम विशेष संस्करण संख्याओं के साथ रिकॉर्ड डालने के द्वारा कवर करना चाहते हैं। फिर, यह बार-बार परिणाम निर्धारित अंतराल के बीच अंतराल को दिखाता है और, उन्हें मूल डेटा सेट से सबसे हाल ही रिकॉर्ड के साथ भरने के लिए प्रयास करता है, जब तक वहाँ कोई और अधिक अंतराल या कोई और अधिक रिकॉर्ड जोड़ने के लिए कर रहे हैं:

GO 
-- Create data set and results table 
CREATE TABLE #Data (
    groupId INT 
    ,startDate DATE 
    ,endDate DATE 
    ,versionId INT 
) 

INSERT INTO #Data (groupId, startDate, endDate, versionId) VALUES (1, '2007-12-22', '2008-12-22', 8) 
INSERT INTO #Data (groupId, startDate, endDate, versionId) VALUES (1, '2008-12-22', '2009-12-22', 9) 
INSERT INTO #Data (groupId, startDate, endDate, versionId) VALUES (1, '2009-12-22', '2010-12-22', 10) 
INSERT INTO #Data (groupId, startDate, endDate, versionId) VALUES (1, '2010-12-22', '2011-12-22', 11) 
INSERT INTO #Data (groupId, startDate, endDate, versionId) VALUES (1, '2011-01-01', '2011-11-30', 500) 
INSERT INTO #Data (groupId, startDate, endDate, versionId) VALUES (1, '2011-12-22', '2012-12-22', 12) 
INSERT INTO #Data (groupId, startDate, endDate, versionId) VALUES (1, '2012-01-22', '2012-12-22', 13) 
INSERT INTO #Data (groupId, startDate, endDate, versionId) VALUES (1, '2012-01-22', '2012-12-22', 14) 
INSERT INTO #Data (groupId, startDate, endDate, versionId) VALUES (1, '2012-04-22', '2012-12-22', 17) 
INSERT INTO #Data (groupId, startDate, endDate, versionId) VALUES (1, '2012-04-22', '2012-12-22', 19) 
INSERT INTO #Data (groupId, startDate, endDate, versionId) VALUES (2, '2010-01-01', '2011-01-01', 1) 
INSERT INTO #Data (groupId, startDate, endDate, versionId) VALUES (2, '2010-10-01', '2011-07-05', 2) 
INSERT INTO #Data (groupId, startDate, endDate, versionId) VALUES (2, '2011-07-05', '2012-08-13', 3) 
INSERT INTO #Data (groupId, startDate, endDate, versionId) VALUES (2, '2012-08-13', '2012-12-31', 6) 
INSERT INTO #Data (groupId, startDate, endDate, versionId) VALUES (2, '2012-10-01', '2012-11-01', 8) 


CREATE TABLE #Results (
    groupId  VARCHAR(10) 
    ,startDate DATE 
    ,endDate DATE 
    ,versionId  BIGINT 
) 

DECLARE @startDate  DATE 
DECLARE @endDate  DATE 
DECLARE @placeholderId BIGINT 

SET @startDate = '20030101' 
SET @endDate = '20121231' 
SET @placeholderId = 999999999999999 

INSERT #Results 
SELECT DISTINCT 
    groupId 
    ,CASE WHEN MIN(startDate) < @startDate THEN MIN(startDate) ELSE @startDate END 
    ,CASE WHEN MIN(startDate) < @startDate THEN @startDate ELSE MIN(startDate) END 
    ,@placeholderId 
FROM #data 
GROUP BY groupId 
UNION ALL 
SELECT DISTINCT 
    groupId 
    ,CASE WHEN MAX(endDate) < @endDate THEN MAX(endDate) ELSE @endDate END 
    ,CASE WHEN MAX(endDate) < @endDate THEN @endDate ELSE MAX(endDate) END 
    ,@placeholderId 
FROM #data 
GROUP BY groupId 
GO 

-- Fill gaps in results table 
DECLARE @startDate  DATE 
DECLARE @endDate  DATE 
DECLARE @placeholderId BIGINT 

SET @startDate = '20030101' 
SET @endDate = '20111231' 
SET @placeholderId = 999999999999999 

DECLARE @counter INT 
SET @counter = 0 

WHILE @counter < 10 
BEGIN 
    SET @counter = @counter + 1; 
    WITH Gaps AS (
     SELECT 
      gs.groupId 
      ,gs.startDate 
      ,MIN(ge.endDate) as endDate 
      ,ROW_NUMBER() OVER (ORDER BY gs.groupId, gs.startDate) as gapId 
     FROM (
      SELECT groupId, endDate as startDate 
      FROM #Results r1 
      WHERE NOT EXISTS (
        SELECT * 
        FROM #Results r2 
        WHERE r2.groupId = r1.groupId 
         AND r2.versionId <> r1.versionId 
         AND r2.startDate <= r1.endDate 
         AND r2.endDate > r1.endDate 
       ) 
       AND NOT (endDate >= @endDate AND versionId = @placeholderId) 
     ) gs 
     INNER JOIN (
      SELECT groupId, startDate as endDate 
      FROM #Results r1 
      WHERE NOT EXISTS (
        SELECT * 
        FROM #Results r2 
        WHERE r2.groupId = r1.groupId 
         AND r2.versionId <> r1.versionId 
         AND r2.endDate >= r1.startDate 
         AND r2.startDate < r1.startDate 
       ) 
       AND NOT (startDate <= @startDate AND versionId = @placeholderId) 
     ) ge 
      ON ge.groupId = gs.groupId 
      AND ge.endDate >= gs.startDate 
     GROUP BY gs.groupId, gs.startDate 
    ) 
    INSERT #Results (
     groupId 
     ,startDate 
     ,endDate 
     ,versionId 
    ) 
    SELECT 
     d.groupId 
     ,CASE WHEN d.startDate < g.startDate THEN g.startDate ELSE d.startDate END 
     ,CASE WHEN d.endDate > g.endDate THEN g.endDate ELSE d.endDate END 
     ,d.versionId 
    FROM #Data d 
    INNER JOIN Gaps g 
     ON g.groupId = d.groupId 
     AND g.startDate <= d.endDate 
     AND g.endDate >= d.startDate 
    INNER JOIN (
     SELECT 
      d.groupId 
      ,gapId 
      ,MAX(d.versionId) as versionId 
     FROM #Data d 
     INNER JOIN Gaps g 
      ON g.groupId = d.groupId 
      AND g.startDate <= d.endDate 
      AND g.endDate >= d.startDate 
     WHERE d.versionId < (
       SELECT MIN(versionId) 
       FROM #Results r 
       WHERE r.groupId = d.groupId 
        AND (r.startDate = g.endDate OR r.endDate = g.startDate) 
      ) 
      AND NOT EXISTS (
       SELECT * 
       FROM #Data dsup 
       WHERE dsup.groupId = d.groupId 
        AND dsup.versionId > d.versionId 
        AND dsup.startDate <= d.startDate 
        AND dsup.endDate >= d.endDate 
      ) 
     GROUP BY 
      d.groupId 
      ,g.gapId 
    ) mg 
     ON mg.groupId = g.groupId 
     AND mg.gapId = g.gapId 
     AND mg.versionId = d.versionId 
END 

SELECT * 
FROM #Results 
WHERE versionId <> @placeholderId 
order by groupId, startDate 

एक सेट के आधार पर समाधान अधिक उपयोगी होगा, लेकिन मैंने एक खोजने के लिए संघर्ष किया है। कोई विचार?

उत्तर

4
-- create a dates table 
create table dates (thedate date primary key clustered); 
;with dates(thedate) as (
    select dateadd(yy,years.number,0)+days.number 
    from master..spt_values years 
    join master..spt_values days 
     on days.type='p' and days.number < datepart(dy,dateadd(yy,years.number+1,0)-1) 
    where years.type='p' and years.number between 100 and 150 
     -- note: 100-150 creates dates in the year range 2000-2050 
     --  adjust as required 
) 
insert dbo.dates select * from dates; 

-- for each date, determine the prevailing version 
    select t.groupId, d.thedate, max(t.versionId) versionId 
    into #tmp1 
    from dates d 
    join #Data t on t.startDate <= d.thedate and d.thedate <= t.endDate 
group by t.groupId, d.thedate; 

-- create index to help 
create clustered index cix_tmp1 on #tmp1(groupId, thedate, versionId); 

-- find the start dates 
;with t as (
    select a.*, rn=row_number() over (partition by a.groupId order by a.thedate) 
    from #tmp1 a 
left join #tmp1 b on b.thedate = dateadd(d,-1,a.thedate) and a.groupId = b.groupId and a.versionId = b.versionId 
    where b.versionId is null 
) 
    select c.groupId, c.thedate startdate, dateadd(d,-1,d.thedate) enddate, c.versionId 
    from t c 
left join t d on d.rn=c.rn+1 and c.groupId = d.groupId 
order by groupId, startdate; 

बेशक, आप "एक क्वेरी" में सब कुछ है, लेकिन, अपने जोखिम पर यह कर के रूप में प्रदर्शन नाली, बड़ा समय नीचे चला जाता है कर सकते हैं।

का उपयोग न करें - शैक्षणिक हित के लिए केवल-

;with dates(thedate) as (
    select dateadd(yy,years.number,0)+days.number 
    from master..spt_values years 
    join master..spt_values days 
     on days.type='p' and days.number < datepart(dy,dateadd(yy,years.number+1,0)-1) 
    where years.type='p' and years.number between 100 and 150 
     -- note: 100-150 creates dates in the year range 2000-2050 
     --  adjust as required 
), tmp1 as (
    select t.groupId, d.thedate, max(t.versionId) versionId 
    from dates d 
    join #Data t on t.startDate <= d.thedate and d.thedate <= t.endDate 
group by t.groupId, d.thedate 
), t as (
    select a.*, rn=row_number() over (partition by a.groupId order by a.thedate) 
    from tmp1 a 
left join tmp1 b on b.thedate = dateadd(d,-1,a.thedate) and a.groupId = b.groupId and a.versionId = b.versionId 
    where b.versionId is null 
) 
    select c.groupId, c.thedate startdate, dateadd(d,-1,d.thedate) enddate, c.versionId 
    from t c 
left join t d on d.rn=c.rn+1 and c.groupId = d.groupId 
order by groupId, startdate; 
+0

http://sqlfiddle.com/#!6/94431/1 – Laurence

+0

के लिए धन्यवाद त्वरित प्रतिक्रिया! परीक्षण डेटा के साथ दौड़ें, परिणाम बहुत अच्छा लग रहा है। मैं इसे बाद में अपने बड़े डेटा सेट के साथ चलाऊंगा और अपने पुनरावर्तक समाधान और आपके बहु-क्वेरी समाधान के लिए प्रदर्शन परिणाम पोस्ट करूंगा। – ExcelValdez

+0

यदि संस्करणों में अंतराल हो सकता है, तो समाप्ति दिनांक गणना विफल हो जाती है। यह है, उदाहरण डेटा में ऐसा नहीं होता है, हालांकि: http://sqlfiddle.com/#!6/ec8dc/1 – Laurence

1

अपडेट किया गया टिप्पणियों से कुछ प्रतिक्रिया की वजह से। मैं अंत मामलों के बारे में चिंता करने वाला नहीं हूं कि कुछ लोगों ने ध्यान दिया है क्योंकि वे अन्य उत्तरों में हल करने के लिए तुच्छ साबित हुए हैं, लेकिन मैं आगे बढ़ना चाहता था और एक वर्किंग वर्जन प्राप्त करना जिसके लिए डीडीएल की आवश्यकता नहीं थी। .. मुझे लगता है कि विकल्पों के लिए यह अच्छा है। :-)

इस कोड को काम करना चाहिए:

select nesty.groupId, nesty.startDate, nesty.segment_end_date, Max(bob.versionId) 
from(
select starter.groupId, starter.startDate, 
coalesce(DATEADD(DAY,-1,ender.startDate),('2012-12-31')) AS segment_end_date 
from 
(select groupId, startDate, ROW_NUMBER() over (partition by groupID order by startDate) as rownumber from 
    (select groupID, startDate from #Data union select groupID, DATEADD(DAY, 1,endDate) as startDate from #Data) xx) starter 
left outer join 
(select groupId, startDate, ROW_NUMBER() over (partition by groupID order by startDate) as rownumber from 
    (select groupID, startDate from #Data union select groupID, DATEADD(DAY, 1,endDate) as startDate from #Data) xy) ender on 
    starter.groupId = ender.groupId and 
    starter.rownumber = ender.rownumber - 1 
where 
starter.startDate<= coalesce(DATEADD(DAY,-1,ender.startDate),('2012-12-31')) 
) nesty 
left outer join #Data bob on 
bob.groupId = nesty.groupId and 
nesty.segment_end_date between bob.startDate and bob.endDate 
group by nesty.groupId, nesty.startDate, nesty.segment_end_date 
order by nesty.groupId, nesty.startDate 

छोटे चेतावनियां मैं इसे एक भी एसक्यूएल बयान में पाने के लिए करना पड़ा के एक जोड़े हैं। सबसे पहले, अधिकतम समाप्ति तिथि गतिशील नहीं है; मैंने '2012-12-31' को कड़ी मेहनत की। आप इसे MAX (एंडडेट) के साथ प्रतिस्थापित कर सकते हैं, लेकिन आप इसे GROUP BY कथन में नहीं डाल सकते हैं। यदि आप एक प्रक्रिया में ऐसा कर सकते हैं, तो आप कर सकते हैं:

select into @max_end_date MAX(endDate) from #Data 

और @max_end_date साथ '2012-12-31' की जगह ले।

दूसरा, मैं गारंटी नहीं देता कि दो आसन्न खंडों का एक ही मूल्य नहीं होगा! यह आपके लिए महत्वपूर्ण हो सकता है या नहीं भी हो सकता है ...

Interval 1:  111111  
Interval 2: 22222222222222 

आपका आउटपुट होगा::

Interval 1: 2222 
Interval 2:  2222222222 

फिर भी, मुझे लगता है कि यह एक सरल और प्रभावी SQL क्वेरी में इसे मारने के लायक है कि है, अगर आप निम्न था। उन चेतावनियों को ठीक करना मुश्किल नहीं हो सकता है, लेकिन इससे कोई फर्क नहीं पड़ता कि मैं क्या काम कर रहा था, इसलिए मैंने अभी तक परेशान नहीं किया है।

+0

यह उदाहरण डेटा के साथ काम नहीं कर रहा है, इसे अंत में v6 पर वापस फ़्लिप करना चाहिए: http://sqlfiddle.com/#!6/9d2a2/1 – Laurence

+0

आह, आप सही हैं ... मैं ' मैं इसके बारे में bummed। इसके अलावा, दूसरे उत्तर पर टिप्पणियों के अनुसार, यह समाधान जल्द से जल्द प्रारंभ और नवीनतम अंत तिथियों के बीच की तारीखों के लिए काम नहीं करेगा जहां कोई संस्करण नहीं है। अब इसे फिर से लिखने का समय नहीं है, लेकिन मैं बाद में एक स्टैब ले सकता हूं। – Chipmonkey

+0

मुझे नहीं पता कि आपने ओपी की टेबल पर इसका अनुवाद करते समय कुछ गलत कॉपी किया है, लेकिन यह काम नहीं करता है, निम्नलिखित संस्करण 1 को बिल्कुल नहीं दिखाना चाहिए http://sqlfiddle.com/#!6/ 5b345/1। किसी भी उदाहरण में कोई अंतराल नहीं है। – Laurence

0

समाप्ति तिथियाँ महत्वपूर्ण है, साथ ही अंतराल हैं, तो यहां एक तरह से आप यह कर सकते है। यह समाधान भी करता है, तो अपने संस्करणों datetimes के बजाय सिर्फ तारीखें काम करने के लिए अनुकूलित किया जा सकता।

पहले कार्यों

एक का एक समूह दो datetimes (मिनट संकल्प) के मध्य पाने के लिए किसी दिए गए दिनांक

Create Function dbo.VersionAtDate(@GroupID int, @Date datetime) Returns int as 
Begin 
    Declare @Ret int = Null 
    Select 
    @Ret = Max(VersionID) 
    From 
    VersionedIntervals iv 
    Where 
    iv.GroupID = @GroupID And 
    iv.StartDate <= @Date And 
    iv.EndDate + 1 > @Date -- if dates were half open intervals this would just be iv.EndDate > @Date 
    Return @Ret 
End 

अगला पर संस्करण प्राप्त करने के:

Create Function dbo.Midpoint(@Start datetime, @End datetime) Returns datetime as 
Begin 
    Return DateAdd(Minute, DateDiff(Minute, @Start, @End)/2, @Start) 
End 

संस्करण

Create Function dbo.VersionAtMidpoint(@GroupID int, @Start datetime, @End datetime) returns int as 
Begin 
    Return dbo.VersionAtDate(@GroupID, dbo.Midpoint(@Start, @End)) 
End; 
: एक मध्य में

अंत में एक मेज महत्वपूर्ण तथ्य यह है कुछ अंक एक सीमा का आरंभ और एक अन्य के अंत कर रहे हैं, और यह इस के लिए एक इनपुट से दो पंक्तियों पाने के लिए मदद करता है कि के साथ मदद करने के लिए समारोह: यह सब के साथ

-- returns two rows if a point is the end of one interval and the 
-- start of another 
Create Function dbo.EndPoints(@GroupID int, @RN bigint, @Start datetime, @End datetime, @Next datetime, @Version int) 
Returns @EndPoints Table (
    GroupID int, 
    RN bigint, 
    Version int, 
    StartDate datetime, 
    EndDate datetime 
) As 
Begin 
    Declare @NextVersion int, @VersionAtMidpoint int 
    Set @NextVersion = dbo.VersionAtDate(@GroupID, @Next) 
    If @NextVersion = @Version 
    -- interval carries on 
    Insert Into @EndPoints Select @GroupID, @RN, @Version, @Start, @Next 
    Else 
    Begin 
    -- interval has ended 
    Set @VersionAtMidpoint = dbo.VersionAtMidPoint(@GroupID, @End, @Next) 
    If @VersionAtMidpoint != @Version 
     -- we have something like this, start a run of 3s (run of 4s is already ended by previous call) 
     -- 3333333 
     -- 44  
     Insert Into @EndPoints Select @GroupID, @RN, @VersionAtMidpoint, @End, @Next 
    Else 
    Begin 
     -- We have something like this, end the run of 3s and start the run of fours 
     -- 33333 
     -- 444 
     Insert Into @EndPoints Select @GroupID, -1, @Version, @Start, @Next 
     Insert Into @EndPoints Select @GroupID, @RN, @NextVersion, @Next, @Next 
    End 
    End 
    Return 
End 

जगह में मशीनरी, अंत में एक पुनरावर्ती CTE Plust तालिका चर, आप उचित रूप से maxrecursion सेट करना होगा:

Declare @Bounds Table (GroupID int, RN bigint, BoundDate datetime, Primary Key (GroupID, RN)) 

Insert Into 
    @Bounds 
Select 
    GroupID, 
    Row_Number() Over (Partition By GroupID Order By BoundDate), 
    BoundDate 
From (
    Select 
     GroupID, 
     StartDate As BoundDate 
    From 
     dbo.VersionedIntervals 
    Union 
    Select 
     GroupID, 
     EndDate 
    From 
     dbo.VersionedIntervals 
    ) a 

;With VersionedBounds (GroupID, RN, StartDate, EndDate, Version) as (
    Select 
     GroupID, 
     RN, 
     BoundDate, 
     BoundDate, 
     dbo.VersionAtDate(GroupID, BoundDate) 
    From 
     @Bounds 
    Where 
     RN = 1 
    Union All 
    Select 
     e.GroupID, 
     e.RN, 
     e.StartDate, 
     e.EndDate, 
     e.Version 
    From 
     @Bounds b 
      Inner Join 
     VersionedBounds v 
      On v.GroupID = b.GroupID And b.RN = v.RN + 1 
      Cross Apply 
     dbo.EndPoints(v.GroupID, b.RN, v.StartDate, v.EndDate, b.BoundDate, v.Version) e 
) 
Select 
    GroupID, 
    StartDate, 
    Max(EndDate) As EndDate, 
    Max(Version) As Version 
From 
    VersionedBounds 
Group By 
    GroupID, 
    StartDate 
Order By 
    GroupID, 
    StartDate 

http://sqlfiddle.com/#!6/b95bd/2

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