2011-08-31 10 views
11

Ho una tabella con 2 colonne di numeri interi. La prima colonna rappresenta l'indice iniziale e la seconda colonna rappresenta l'indice finale.Numero chiuso gruppo

START END 
1  8 
9  13 
14 20 
20 25 
30 42 
42 49 
60 67 

Semplice Finora. Quello che vorrei fare è raggruppare tutti i record che seguono insieme:

START END 
1  25 
30 49 
60 67 

Un record può seguire avviando sullo stesso indice come il precedente dell'indice finale o con un margine di 1:

START END 
1  10 
10 20 

E

START END 
1  10 
11 20 

saranno entrambi risultato

START END 
1  20 

Utilizzo SQL Server 2008 R2.

Qualsiasi aiuto sarebbe grande

+4

Penso che questa sia una domanda interessante, ma hai già fatto qualche tentativo di farlo da solo? Query che hai provato? – jadarnel27

+2

Potresti avere coppie sovrapposte come '1,8' AND' 3,15'? –

+1

Tx per il tuo commento Martin .. Non ci sono coppie sovrapposte. Jadarnel27 - Ho risolto questo problema usando il cursore sql ma questa soluzione non è affatto efficiente e sto cercando una soluzione più elegante e migliore. –

risposta

3

Questo funziona per il tuo esempio, fammi sapere se non funziona per altri dati

create table #Range 
(
    [Start] INT, 
    [End] INT 
) 

insert into #Range ([Start], [End]) Values (1, 8) 
insert into #Range ([Start], [End]) Values (9, 13) 
insert into #Range ([Start], [End]) Values (14, 20) 
insert into #Range ([Start], [End]) Values (20, 25) 
insert into #Range ([Start], [End]) Values (30, 42) 
insert into #Range ([Start], [End]) Values (42, 49) 
insert into #Range ([Start], [End]) Values (60, 67) 



;with RangeTable as 
(select 
    t1.[Start], 
    t1.[End], 
    row_number() over (order by t1.[Start]) as [Index] 
from 
    #Range t1 
where t1.Start not in (select 
         [End] 
       from 
        #Range 
        Union 
       select 
        [End] + 1 
       from 
        #Range 
       ) 
) 
select 
    t1.[Start], 
    case 
    when t2.[Start] is null then 
     (select max([End]) 
        from #Range) 
     else 
     (select max([End]) 
        from #Range 
        where t2.[Start] > [End]) 
end as [End]  
from 
    RangeTable t1 
left join 
    RangeTable t2 
on 
    t1.[Index] = t2.[Index]-1 

drop table #Range; 
+0

Ciao Aducci, La tua soluzione funziona bene anche per altri dati più grandi dei dati nelle tabelle di esempio. –

+0

@Liran Ben Yehuda - C'era una ragione per cui l'hai smarcato come una risposta? – Aducci

+0

Tx per il vostro supporto. Cerco solo la soluzione migliore e devo fare alcuni test delle prestazioni prima. –

4

modificato per includere un'altra versione e penso che sia un po 'più affidabile, e funziona anche con sovrapposizione gamme

CREATE TABLE #data (start_range INT, end_range INT) 
INSERT INTO #data VALUES (1,8) 
INSERT INTO #data VALUES (2,15) 
INSERT INTO #data VALUES (9,13) 
INSERT INTO #data VALUES (14,20) 
INSERT INTO #data VALUES (13,26) 
INSERT INTO #data VALUES (12,21) 
INSERT INTO #data VALUES (9,25) 
INSERT INTO #data VALUES (20,25) 
INSERT INTO #data VALUES (30,42) 
INSERT INTO #data VALUES (42,49) 
INSERT INTO #data VALUES (60,67) 

;with ranges as 
(
SELECT start_range as level 
,end_range as end_range 
,row_number() OVER (PARTITION BY (SELECT NULL) ORDER BY start_range) as row 
FROM #data 
UNION ALL 
SELECT 
level + 1 as level 
,end_range as end_range 
,row 
From ranges 
WHERE level < end_range 
) 
,ranges2 AS 
(
SELECT DISTINCT 
level 
FROM ranges 
) 
,ranges3 AS 
(
SELECT 
level 
,row_number() OVER (ORDER BY level) - level as grouping_group 
from ranges2 
) 
SELECT 
MIN(level) as start_number 
,MAX(level) as end_number 
FROM ranges3 
GROUP BY grouping_group 
ORDER BY start_number ASC 

Penso che questo dovrebbe funzionare - potrebbe non essere particolarmente efficace su set più grandi però ...

CREATE TABLE #data (start_range INT, end_range INT) 
INSERT INTO #data VALUES (1,8) 
INSERT INTO #data VALUES (2,15) 
INSERT INTO #data VALUES (9,13) 
INSERT INTO #data VALUES (14,20) 
INSERT INTO #data VALUES (21,25) 
INSERT INTO #data VALUES (30,42) 
INSERT INTO #data VALUES (42,49) 
INSERT INTO #data VALUES (60,67) 


;with overlaps as 
(
select * 
,end_range - start_range as range 
,row_number() OVER (PARTITION BY (SELECT NULL) ORDER BY start_range ASC) as line_number 
from #data 
) 
,overlaps2 AS 
(
SELECT 
O1.start_range 
,O1.end_range 
,O1.line_number 
,O1.range 
,O2.start_range as next_range 
,CASE WHEN O2.start_range - O1.end_range < 2 THEN 1 ELSE 0 END as overlap 
,O1.line_number - DENSE_RANK() OVER (PARTITION BY (CASE WHEN O2.start_range - O1.end_range < 2 THEN 1 ELSE 0 END) ORDER BY O1.line_number ASC) as overlap_group 
FROM overlaps O1 
LEFT OUTER JOIN overlaps O2 on O2.line_number = O1.line_number + 1 
) 
SELECT 
MIN(start_range) as range_start 
,MAX(end_range) as range_end 
,MAX(end_range) - MIN(start_range) as range_span 
FROM overlaps2 
GROUP BY overlap_group 
+0

+1 Testato qui e ha funzionato. Per fortuna hai incluso le istruzioni 'CREATE' e' INSERT'. –

+0

Hi Davin, La tua seconda soluzione è molto più affidabile poiché la prima non ha funzionato bene. In realtà, le tabelle originali non contengono alcuna sovrapposizione. Se hai qualche idea su come resolare il problema senza sovrapposizioni in modo più efficiente mi piacerebbe sapere. Tx per il vostro aiuto :) –

+0

@Liran Ben Yehuda - nella tua domanda iniziale volevi esempi di 1-10,11-20 E 1-10,10-20 per dare un intervallo di 1-20 - quindi c'è sovrapposizione come nel secondo caso 10 appare due volte, questo significa che nelle tue tabelle reali ogni valore di inizio e fine è unico? – Dibstar

3

È possibile utilizzare un number table per risolvere questo problema. Fondamentalmente, prima espandi gli intervalli, quindi combina gli elementi successivi in ​​gruppi.

Ecco uno attuazione:

WITH data (START, [END]) AS (
    SELECT 1, 8 UNION ALL 
    SELECT 9, 13 UNION ALL 
    SELECT 14, 20 UNION ALL 
    SELECT 20, 25 UNION ALL 
    SELECT 30, 42 UNION ALL 
    SELECT 42, 49 UNION ALL 
    SELECT 60, 67 
), 
expanded AS (
    SELECT DISTINCT 
    N = d.START + v.number 
    FROM data d 
    INNER JOIN master..spt_values v ON v.number BETWEEN 0 AND d.[END] - d.START 
    WHERE v.type = 'P' 
), 
marked AS (
    SELECT 
    N, 
    SeqID = N - ROW_NUMBER() OVER (ORDER BY N) 
    FROM expanded 
) 
SELECT 
    START = MIN(N), 
    [END] = MAX(N) 
FROM marked 
GROUP BY SeqID 

Questa soluzione utilizza master..spt_values come una tabella numerica, per espandere le gamme iniziali. Ma se (tutto o parte di) tali intervalli possono comprendere più di 2048 (successivi) valori, è necessario definire e utilizzare la tabella dei numeri your own.