Поиск непрерывных диапазонов в наборе чисел

У меня достаточно большой набор телефонных номеров (примерно 2 миллиона) в таблице базы данных. Эти числа были вставлены в блоки, поэтому существует много непрерывных диапазонов чисел, от 10 чисел до 10 тысяч в диапазоне. Некоторые из этих номеров используются и поэтому помечены как недоступные, остальные доступны. Учитывая конкретное число, Мне нужен способ найти непрерывные диапазоны чисел, как выше, так и ниже этого числа. Диапазон должен продолжаться до тех пор, пока он находит недоступное число или встречается с границей двух диапазонов.

например, учитывая следующий набор:

1000
1001
1002
1010
1011
1012
1013
1020
1021
1022

выполнение поиска с использованием 1012 в качестве параметра должно возвращать 1010, 1011, 1012, 1013.

каков хороший способ формирования запроса для поиска этих диапазонов? Мы используем NHibernate поверх SQL server, решение, использующее любой из них, прекрасно.

4 ответов


теоретически элементы в наборе не имеют определенного значения, поэтому я предполагаю, что у вас также есть непрерывный столбец ID, который определяет порядок чисел. Что-то вроде этого:--4-->

ID  Number
1   1000
2   1001
3   1002
4   1010
5   1011
6   1012
7   1013
8   1020
9   1021
10  1022

вы можете создать дополнительный столбец, содержащий результат Number - ID:

ID  Number  Diff
1   1000    999
2   1001    999
3   1002    999
4   1010    1006
5   1011    1006
6   1012    1006
7   1013    1006
8   1020    1012
9   1021    1012
10  1022    1012

числа в том же диапазоне будут иметь тот же результат в столбце Diff.


SQL не может сделать это в одном запросе (за исключением собственных улучшений SQL, о которых я не знаю), потому что SQL не может получить доступ к строке " до " или "после".

нужно пройти последовательность в цикле.

вы можете попробовать NHibernates Enumerable, который не загружает сущности в память, а только создает их прокси. На самом деле я не думаю, что это хорошая идея, потому что она создаст прокси для целых 2 миллионов номеров.

план B, используйте пейджинг. Примерно это выглядит так:

List<PhoneNumber> result = new List<PhoneNumber>();

int input = 1012;
int pageSize = 100;
int currentPage = 0;
int expectedNumber = input;

bool carryOn = true;

while(carryOn)
{
  var numbers = session
    .CreateQuery("from PhoneNumber pn where pn.Number > :input")
    .SetInt("input", input)
    .SetFirstResult(currentPage * pageSize)
    .SetMaxResult(pageSize)
    .List<PhoneNumbers>();

  foreach(var number in numbers)
  {
    expectNumber++;
    if (number.Number != expectedNumber) 
    {
      carryOn = false;
      break;
    }
    result.Add(number);
  }

  currentPage++;
}

и то же самое для диапазона в другую сторону.


Если вы используете SQL server, вы должны иметь возможность сделать рекурсивный запрос который присоединится к root.количество = лист.количество + 1

Если вы выбираете число из корня и из последней рекурсии, а также уровень рекурсии, у вас должен быть рабочий запрос.

Я бы сначала протестировал производительность этого, а затем, если не удовлетворительно, повернулся к подходу на основе курсора / строки (который в этом случае выполнял бы работу с одним полным сканированием, где рекурсия может завершиться неудачей достижение максимальной глубины рекурсии).

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

Это может быть реализовано в триггерах с не таким высоким штрафом при обновлении одной строки (обновления в одной строке базовой таблицы будут обновлять, удалять или разбивать строку в таблице min-max; Это может быть определено путем запроса только "предыдущей" и "следующей" строки).


используйте вспомогательную таблицу всех возможных последовательных значений или материализуйте ее в CTE, например

WITH
-- materialize a table of sequential integers
l0 AS (SELECT 0 AS c UNION ALL SELECT 0),
l1 AS (SELECT 0 AS c FROM l0 AS a, l0 AS b),
l2 AS (SELECT 0 AS c FROM l1 AS a, l1 AS b),
l3 AS (SELECT 0 AS c FROM l2 AS a, l2 AS b),
l4 AS (SELECT 0 AS c FROM l2 AS a, l3 AS b),
l5 AS (SELECT 0 AS c FROM l2 AS a, l4 AS b),
nums AS (SELECT row_number() OVER(ORDER BY c) AS n FROM l5), 
-- materialize sample table
MyTable (ID) AS 
(
 SELECT 1000
 UNION ALL 
 SELECT 1001
 UNION ALL 
 SELECT 1002
 UNION ALL 
 SELECT 1010
 UNION ALL 
 SELECT 1011
 UNION ALL 
 SELECT 1012
 UNION ALL 
 SELECT 1013
 UNION ALL 
 SELECT 1020
 UNION ALL 
 SELECT 1021
 UNION ALL 
 SELECT 1022
), 
-- materialize parameter table
params (param) AS (SELECT 1012)
SELECT MIN(N1.n) - 1 AS last_in_sequence
  FROM nums AS N1 
       CROSS JOIN params AS P1
 WHERE N1.n > P1.param
       AND NOT EXISTS 
       (
        SELECT * 
          FROM MyTable AS T1
         WHERE N1.n = T1.ID
       );