【问题标题】:Given a subnet range and a list of IPs select all rows which the IPs fall between给定子网范围和 IP 列表,选择 IP 所在的所有行
【发布时间】:2013-06-09 04:37:30
【问题描述】:

我正在对数据库中网络的各个方面进行建模。我们正在处理的一个更烦人的问题是创建子网范围,然后确定一组给定的 IP 是否在这些范围内。我们当前的模型通过以下列解释了 IPv4 和 IPv6 之间的差异:

[subnet_sk]      [int] IDENTITY(1,1) NOT NULL,
[ipv6_network]   [char](39)          NULL,
[ipv6_broadcast] [char](39)          NULL,
[ipv4_network]   [char](15)          NULL,
[ipv4_broadcast] [char](15)          NULL,
[network_type]   [char](4)           NOT NULL

上面的架构做了一些重要的假设。我们正在使用完全扩展的 IP(192.168.001.001192.168.1.1)进行存储和比较。我们之所以做出这个决定是因为存储 IPv6 addresses numerically in SQL server 的问题(bigint 是无符号的,这意味着我们必须使用六列来表示 IPv6)。

鉴于此表架构,很容易编写一个选择语句来确定任一类型的 IP 是否在表中的范围之间:

select *
  from subnet
 where '1234:0000:0000:0000:fc12:00ab:0042:1050'
       between ipv6_network
           and ipv6_broadcast

-- or alternatively for IPv4

select *
  from subnet
 where '192.168.005.015'
       between ipv4_network
           and ipv4_broadcast

更难的是给出一个 IP 列表来确定其中哪些位于子网范围之间。 IP 列表将由用户输入提供,并且存储在数据库中。显然,对于存储在数据库中的数据,我可以进行类似的连接,如下例所示。

例如,用户可以提供1234:0000:0000:0000:fc12:00ab:0042:1050192.168.001.001192.168.1.1。我想出的唯一解决方案是使用 table-valued function to split a list 的 IP 并使用 between 执行连接:

-- this only covers the IPv4 addresses from the above list a similar query would
-- be used for IPv6 and the two queries could be unioned
select sub.*
  from fn_SplitList('192.168.001.001,192.168.005.015',',') split
       join subnet sub
         on split.Data
             between sub.ipv4_network
                 and sub.ipv4_broadcast

虽然使用拆分功能有效,但感觉很笨拙。我花了大部分时间在common table expressions 周围嗅探,但想不出一个可行的实现。理想情况下,单个选择将确定是否从 IPv4 或 IPv6 列中反弹给定的字符串,但如果这不可能,我可以在将 IP 集合交给数据库之前分离列表。

为了更容易回答,我创建了上面的SQL Fiddle。 SQL 中是否有一种机制(我宁愿不使用 T-SQL)给定 IP 列表来确定这些 IP 位于哪些现有子网范围?上述模式是否是解决问题的正确方法,不同的数据模型会导致更简单的解决方案吗?

【问题讨论】:

  • 我没有很好的网络知识。我想知道是只有 fn_SplitList() 困扰您,还是您想找到一些更好的方法来处理整个场景?
  • @RaviSingh 给出了这个数据模型,如果我们使用日期列表来确定它们是否在一个范围内,我也会有同样的问题。所以我的问题是我觉得 fn_SplitList 是解决问题的一种方法,但感觉那里有一个更清洁的解决方案。也就是说,如果您告诉我“您的架构是错误的,并且如果您的数据模型以这种方式设置,您的查询会更容易,”我会全神贯注。因此,如果您无法帮助建模但可以帮助更好地查询不使用 fn_SplitList 我对您的解决方案非常感兴趣。
  • 最近偶然发现,但为了参考,这里有一些类似的线程讨论 SQL Server 中的字符串拆分:stackoverflow.com/questions/2647/split-string-in-sqlstackoverflow.com/questions/314824/…

标签: sql sql-server ipv6 ipv4


【解决方案1】:

这不是一个完整的解决方案,而是另一个设计的想法, 我在想而不是进行典型的 SQL 比较,为什么不尝试使用逻辑比较。 对 sql 实现知之甚少,我尝试过按位比较,(使用 bigint)

还有很多优化要做,但是我认为它可能会有所帮助,

我比较 4 个 ip(192.168.1.1 和另外 3 个)的小演示,我将它们用作 bigint,因为 int 太小,我需要使用逻辑按位比较, (更多信息在这里http://msdn.microsoft.com/en-us/library/ms174965.aspx

select * from (
    select cast(192168001001 as bigint) as ip union all
    select cast(192168001002 as bigint) as ip union all
    select cast(192168002001 as bigint) as ip union all
    select cast(192168002002 as bigint) as ip
) as ip_table
where ip & cast(192168001000 as bigint) = cast(192168001000 as bigint)

如您所见,i (AND/&) IP 和网络地址 然后我将其与网络地址进行比较,如果匹配,则属于此范围

如果我错了,请纠正我,我需要更多地考虑这个,确实非常有趣的东西

编辑: 正如下面评论的,bigint 对于 IPv6 来说太小了, 所以这很遗憾不起作用,按位(AND)运算不能用二进制数据类型完成,它只会接受整数类型......

【讨论】:

  • bigint 非常适合 IPv4 地址。不幸的是,SQL Server 不允许无符号大整数,这使得 IPv6 的按位比较变得困难,因为您需要三列将 IPv6 地址表示为大整数。
【解决方案2】:

我一直在看你的 SQL Fiddle, 玩弄有问题的查询,

为了 100% 清楚,您需要一个查询来查找主机地址列表所属的所有范围。

所以你可以把你的主机当作一个数据列表/表,然后将子网内部连接到它上面(或者如果你需要它出现,即使没有子网也可以左连接)

select *
from (
  select '192.168.001.001' as ip union
  select'192.168.005.015') as hosts
inner join subnet
  on ip between ipv4_network and ipv4_broadcast

我得到了 4 个结果(每个记录有两个匹配的子网)

【讨论】:

    【解决方案3】:

    您是否考虑将 ipv6 和 ipv4 格式存储在一列中?

    Storing IP addresses in Microsoft SQL Server

    这需要转换任意源数据以供您比较(或其他方式),但至少您可以避免需要两个单独的查询来检查。

    然后我倾向于从您的源数据(FOR XML?)形成一个 CTE,然后加入您的数据库表(子网)。

    【讨论】:

      【解决方案4】:

      我倾向于使用 [xml] 或 [heirarchyid] (http://technet.microsoft.com/en-us/library/bb677290.aspx) 来解决这个问题,并将数据视为一棵树。然后使用 [tree].[run] @subnet 方法基于现有子网构建树变得相对简单,该方法运行树并找到与 @subnet 匹配的节点。通过将数据视为一棵树(实际上就是这样)并构建通用的树处理(递归)方法,您应该能够轻松地找到一个节点(如果存在)或插入它并获取下一个和之前的节点。

      如果感兴趣,我可以提供更详细的示例,但这不是一个简单的解决方案,所以我不会花时间在它上面。我在这里展示的是一个简单的原型,它找到作为输入掩码的父节点(作为简单匹配)的节点。我仅将其作为示例提交,但如果您对解决方案感兴趣,我可以提供更多详细信息,或者您可以轻松了解如何使用这些技术构建解决方案。

      和平, 凯瑟琳

          use [test_01];
      
          go
      
          if schema_id(N'tree') is null
              execute (N'create schema tree');
      
          go
      
          if object_id(N'[tree].[run]',
                       N'FN') is not null
              drop function [tree].[run];
      
          go
      
          create function [tree].[run] (
              @network      [xml],
              @mask_to_find [sysname],
              @position     [sysname]
          )
          returns [sysname]
          as
              begin
                  declare @quad [sysname] = substring(@mask_to_find,
                              0,
                              charindex(N'.',
                                        @mask_to_find,
                                        0));
      
                  set @mask_to_find = substring(@mask_to_find,
                                                charindex(N'.', @mask_to_find, 0) + 1,
                                                len(@mask_to_find));
                  set @network = @network.query('/*[@quad=sql:variable("@quad")]/*');
      
                  if(@network.value('count (/*)',
                                    'int') > 0)
                      begin
                          set @position = coalesce(@position + N'.', N'') + @quad;
                      end
                  else
                      set @position = coalesce(@position + N'.', N'') + N'000';
      
                  if (@@nestlevel < 4)
                      return [tree].[run] (@network,
                                           @mask_to_find,
                                           @position);
      
                  return @position;
              end
      
          go
      
          declare @network [xml] = N'<subnet quad="255" >
                  <subnet quad="255" >
                      <subnet quad="192" />
                      <subnet quad="255" />
                  </subnet>
              </subnet>
              <subnet quad="10" />';
          declare @mask_to_find [sysname] = N'255.255.190.000';
          declare @position [sysname];
      
          select [tree].[run] (@network,
                               @mask_to_find,
                               @position)
      
          go 
      

      【讨论】:

        猜你喜欢
        • 2019-08-21
        • 1970-01-01
        • 2015-04-05
        • 2018-10-08
        • 2014-12-31
        • 2010-12-16
        • 2018-08-16
        • 2013-07-12
        • 1970-01-01
        相关资源
        最近更新 更多