我正在为数据库中的网络的各个方面建模。我们正在处理的一个更烦人的问题是创建子网范围,然后确定给定的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.001
与192.168.1.1
)进行存储和比较。我们做出这个决定是因为存储IPv6 addresses numerically in SQL server的问题(bigint是无符号的,意味着我们必须使用六列代表IPv6)。
鉴于此表模式,可以很容易地编写一个select select语句来确定任一类型的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位于子网范围之间。 IP列表将由用户输入提供,并且不存储在数据库中。显然,对于存储在数据库中的数据,我可以进行类似的连接,如下例所示。
例如,用户可以提供1234:0000:0000:0000:fc12:00ab:0042:1050
,192.168.001.001
和192.168.1.1
。我提出的唯一解决方案是使用table-valued function to split a list IP并使用以下之间执行连接:
-- 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。在给定IP列表的情况下,SQL中是否存在一种机制(我不想使用T-SQL)来确定这些IP介于哪个现有子网范围之间?上述模式甚至是解决问题的正确方法,不同的数据模型会导致更容易的解决方案吗?
答案 0 :(得分: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)
你可以看到我(和/或)IP和网络地址 然后我将它与网络地址进行比较,如果它匹配,则它在此范围内
纠正我,如果我错了,我需要更多地考虑这个,非常有趣的东西确实
编辑: 如下所述,bigint对于IPv6来说太小了, 所以这很遗憾不起作用,按位(AND)操作不能用二进制数据类型完成,它只接受整数类型......
答案 1 :(得分:1)
我一直在看你的SQL小提琴, 玩弄有问题的查询,
要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个结果(有两个子网匹配每个记录)
答案 2 :(得分:1)
您是否考虑将ipv6和ipv4格式存储在一列中?
Storing IP addresses in Microsoft SQL Server
这需要转换任意源数据以供您比较(或其他方式),但至少可以避免需要两个单独的查询来检查。
然后我倾向于从您的源数据(FOR XML?)形成CTE,然后加入您的数据库表(子网)。
答案 3 :(得分:0)
我倾向于使用[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