2011-08-31 9 views
11

Tengo una tabla con 2 columnas de números enteros. La primera columna representa el índice de inicio y la segunda columna representa el índice final.Números cercanos de grupo

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

Simple Hasta ahora. Lo que me gustaría hacer es agrupar todos los registros que siguen juntos:

START END 
1  25 
30 49 
60 67 

Un registro puede seguir iniciando en el mismo índice que el índice extremo anterior o por un margen de 1:

START END 
1  10 
10 20 

Y

START END 
1  10 
11 20 

voluntad tanto en el resultado

START END 
1  20 

Estoy usando SQL Server 2008 R2.

Cualquier ayuda sería grande

+4

Creo que esta es una pregunta interesante, pero, ¿has hecho algún intento de hacerlo tú mismo? Consultas que has intentado? – jadarnel27

+2

¿Podría tener pares superpuestos como '1,8' Y' 3,15'? –

+1

Tx para su comentario Martin .. No hay pares superpuestos. Jadarnel27 - Resolví este problema usando el cursor sql pero esta solución no es eficiente en absoluto y estoy buscando una solución más elegante y mejor. –

Respuesta

3

Esto funciona por su ejemplo, que me haga saber si no funciona para otros datos

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

Hola Aducci, Su solución también funciona bien para otros datos más grandes que los datos en las tablas de ejemplo. –

+0

@Liran Ben Yehuda: ¿hubo alguna razón por la que no lo marcó como respuesta? – Aducci

+0

Tx para su apoyo. Solo busco la mejor solución y tengo que hacer algunas pruebas de rendimiento antes. –

4

Editado para incluir otra versión, que creo que es un poco más fiable, y también trabaja con intervalos que se solapan

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 

Creo que esto debería funcionar - podría no ser especialmente eficiente en conjuntos más grandes, aunque ...

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 Probado aquí y funcionó. Menos mal que incluiste las sentencias 'CREATE' y' INSERT'. –

+0

Hola Davin, Su segunda solución es mucho más confiable ya que la primera no funcionó bien. En realidad, las tablas originales no contienen superposiciones. Si tiene alguna idea de cómo resolver el problema sin superposiciones de una manera más eficiente, me gustaría saberlo. Tx por su ayuda :) –

+0

@Liran Ben Yehuda - en su pregunta original quería ejemplos de 1-10,11-20 y 1-10,10-20 para dar un rango de 1-20 - entonces hay superposición en el segundo caso, 10 aparece dos veces, ¿significa esto que en sus tablas reales cada valor de rango inicial y final es único? – Dibstar

3

Se puede usar un number table para resolver este problema. Básicamente, primero expande los rangos y luego combina los elementos subsiguientes en grupos.

Aquí hay una aplicación:

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 

Esta solución utiliza master..spt_values como una tabla de números, para ampliar los rangos iniciales. Pero si (todos o algunos de) esos rangos pueden abarcar más de 2048 (subsiguientes) valores, entonces debe definir y usar your own tabla de números.

Cuestiones relacionadas