Группировать близкие номера

У меня есть таблица с двумя столбцами целых чисел. Первый столбец представляет начальный индекс, а второй столбец представляет конечный индекс.

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

Пока просто. То, что я хотел бы сделать, это сгруппировать все записи, которые следуют вместе:

START END
1     25
30    49
60    67

Запись может начинаться с того же индекса, что и предыдущий конечный индекс, или с отставанием в 1:

START END
1     10
10    20

И

START END
1     10
11    20

оба приведут к

START END
1     20

Я использую SQL Server 2008 R2.

Любая помощь будет отличной


person Liran Ben Yehuda    schedule 31.08.2011    source источник
comment
Я думаю, что это интересный вопрос, но предпринимали ли вы какие-либо попытки сделать это самостоятельно? Запросы, которые вы пробовали?   -  person Josh Darnell    schedule 31.08.2011
comment
Могут ли у вас быть какие-либо пересекающиеся пары, такие как 1,8 И 3,15?   -  person Martin Smith    schedule 31.08.2011
comment
Tx за ваш комментарий, Мартин.. Нет перекрывающихся пар. Jadarnel27 - я решил эту проблему с помощью курсора sql, но это решение совсем неэффективно, и я ищу более элегантное и лучшее решение.   -  person Liran Ben Yehuda    schedule 01.09.2011


Ответы (3)


Это работает для вашего примера, дайте мне знать, если это не работает для других данных.

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;
person Aducci    schedule 31.08.2011
comment
Привет, Адуччи. Ваше решение отлично работает и для других данных, больших, чем данные в таблицах примеров. - person Liran Ben Yehuda; 01.09.2011
comment
@Liran Ben Yehuda - Была ли причина, по которой вы не отметили это как ответ? - person Aducci; 01.09.2011
comment
Спасибо за вашу поддержку. Я просто ищу лучшее решение, и перед этим мне нужно провести несколько тестов производительности. - person Liran Ben Yehuda; 04.09.2011

Отредактировано, чтобы включить другую версию, которая, как мне кажется, немного более надежна, а также работает с перекрывающимися диапазонами

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

Я думаю, что это должно сработать - хотя может быть не особенно эффективным на больших наборах...

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
person Dibstar    schedule 31.08.2011
comment
+1 Проверено здесь, и это сработало. Хорошо, что вы включили операторы CREATE и INSERT. - person Adriano Carneiro; 31.08.2011
comment
Привет, Дэвин. Ваше второе решение намного надежнее, так как первое не сработало. На самом деле исходные таблицы не содержат наложений. Если у вас есть идеи, как решить проблему без дублирования более эффективным способом, я хотел бы знать. ТС за помощь :) - person Liran Ben Yehuda; 01.09.2011
comment
@Liran Ben Yehuda - в своем первоначальном вопросе вы хотели, чтобы примеры 1-10,11-20 И 1-10,10-20 давали диапазон 1-20 - так что есть совпадение, так как во втором случае 10 появляется дважды, означает ли это, что в ваших реальных таблицах каждое значение начального и конечного диапазона уникально? - person Dibstar; 01.09.2011
comment
@Devin - извините за путаницу. Вы правы, я имел в виду, что перекрывающийся диапазон не существует, например, вторая строка (2,15) в вашем примере (также значения строки [9,25]) - person Liran Ben Yehuda; 01.09.2011
comment
@Liran, в этом случае я думаю, что это все равно должно быть относительно эффективным, поскольку CTE будет заполнять пробелы только для существующих диапазонов, а затем видит, какие из них попадают в 1 из предыдущей строки - сколько диапазонов стоит данных вы есть в вашей фактической таблице? - person Dibstar; 01.09.2011
comment
@Devin - Извините, но я не могу оценить, сколько диапазонов данных будет в реальной таблице ... - person Liran Ben Yehuda; 01.09.2011
comment
@Devin - Действительно ли функция Row_Number() в диапазонах CTE необходима, если таблица не содержит нулевых значений? - person Liran Ben Yehuda; 01.09.2011
comment
@Liran, вы можете удалить Row_number из диапазонов CTE, если у вас определенно нет настоящих перекрывающихся строк. Я включил его, чтобы вы могли сказать, из какой строки диапазона получено значение (для проверки перекрытий), чтобы убедиться, что все диапазоны включены. - person Dibstar; 01.09.2011
comment
давайте продолжим это обсуждение в чате - person Liran Ben Yehuda; 01.09.2011

Вы можете использовать таблица чисел, чтобы решить эту проблему. По сути, вы сначала расширяете диапазоны, затем объединяете последующие элементы в группы.

Вот одна реализация:

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

В этом решении используются mea" title="Какова цель системной таблицы table master..spt_values ​​и каково значение ее значений?">master..spt_values в качестве числовой таблицы для расширения начальных диапазонов. Но если (все или некоторые из них) эти диапазоны могут охватывать более 2048 (последующих) значений, вам следует определить и использовать ваша собственная таблица чисел.

person Andriy M    schedule 01.09.2011