查找一组数字中的连续范围

时间:2010-06-29 09:16:08

标签: sql nhibernate set

我在数据库表中有一组相当大的电话号码(大约200万)。这些数字已插入块中,因此有许多连续的数字范围,范围内从10到10万。其中一些号码正在使用中,因此标记为不可用,其余的可用。给定一个特定的数字,我需要一种方法来找到连续的数字范围,包括这个数字的上方和下方。范围应该继续,直到找到不可用的数字,或遇到两个范围的边界。

例如给出以下集合:

1000
1001
1002
1010
1011
1012
1013
1020
1021
1022

使用1012作为参数进行搜索应返回1010,1011,1012,1013。

形成查询以查找这些范围的好方法是什么?我们在SQL服务器上使用NHibernate,使用其中任何一个的解决方案都很好。

4 个答案:

答案 0 :(得分:18)

理论上,集合中的项目没有特定的值,所以我假设你还有一些连续的ID列来定义数字的顺序。像这样:

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列中的结果相同。

答案 1 :(得分:1)

SQL无法在单个查询中真正执行此操作(除了我不知道的本机SQL增强功能),因为SQL无法访问“之前”或“之后”行。

你需要循环完成序列。

您可以尝试NHibernates Enumerable,它不会将实体加载到内存中,而只会创建它们的代理。实际上我认为这不是一个好主意,因为它会为整个200万个数字创建代理。

计划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++;
}

对于之前的另一个方向的范围也一样。

答案 2 :(得分:0)

如果你使用SQL服务器,你应该能够建立一个recursive query加入root.number = leaf.number + 1

如果从根目录和最后一次递归中选择数字,并且递归级别应该有一个有效的查询。

我首先测试它的性能,然后如果不满意则转向基于光标/行的方法(在这种情况下,这将通过单次全扫描完成工作,其中递归可能通过达到最大递归深度而失败)。 / p>

否则,您的选择是以不同方式存储数据,并维护与表关联的最小,最大数字列表。

这实际上可以在单行更新中没有如此高惩罚的触发器中实现(基表的单行上的更新将更新,删除或拆分最小 - 最大表中的行;这可以确定通过仅查询“上一个”和“下一个”行。

答案 3 :(得分:0)

使用所有可能的连续值的辅助表或在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
       );