用于在SQL Server中存储IP地址的数据types
我应该select哪种数据types来在SQL Server中存储IP地址?
通过select正确的数据types,是否容易过滤IP地址呢?
存储IPv4的技术上正确的方法是二进制(4),因为它实际上是(甚至不是INT32 / INT(4)),我们都知道和喜欢的数字文本forms(255.255.255.255)只是它的二进制内容的显示转换。
如果你这样做,你会想要function转换为文本显示格式和从文本显示格式转换:
以下是如何将文本显示forms转换为二进制文件:
CREATE FUNCTION dbo.fnBinaryIPv4(@ip AS VARCHAR(15)) RETURNS BINARY(4) AS BEGIN DECLARE @bin AS BINARY(4) SELECT @bin = CAST( CAST( PARSENAME( @ip, 4 ) AS INTEGER) AS BINARY(1)) + CAST( CAST( PARSENAME( @ip, 3 ) AS INTEGER) AS BINARY(1)) + CAST( CAST( PARSENAME( @ip, 2 ) AS INTEGER) AS BINARY(1)) + CAST( CAST( PARSENAME( @ip, 1 ) AS INTEGER) AS BINARY(1)) RETURN @bin END go
这里是如何将二进制转换回文本显示forms:
CREATE FUNCTION dbo.fnDisplayIPv4(@ip AS BINARY(4)) RETURNS VARCHAR(15) AS BEGIN DECLARE @str AS VARCHAR(15) SELECT @str = CAST( CAST( SUBSTRING( @ip, 1, 1) AS INTEGER) AS VARCHAR(3) ) + '.' + CAST( CAST( SUBSTRING( @ip, 2, 1) AS INTEGER) AS VARCHAR(3) ) + '.' + CAST( CAST( SUBSTRING( @ip, 3, 1) AS INTEGER) AS VARCHAR(3) ) + '.' + CAST( CAST( SUBSTRING( @ip, 4, 1) AS INTEGER) AS VARCHAR(3) ); RETURN @str END; go
这里有一个演示如何使用它们:
SELECT dbo.fnBinaryIPv4('192.65.68.201') --should return 0xC04144C9 go SELECT dbo.fnDisplayIPv4( 0xC04144C9 ) -- should return '192.65.68.201' go
最后,当进行查找和比较时,如果你想能够利用你的索引,总是使用二进制forms。
更新:
我想添加一种方法来解决SQL Server中标量UDF的固有性能问题,但仍然保留函数的代码重用是使用iTVF(内联表值函数)。 以下是第一个函数(string到二进制)如何被重写为iTVF:
CREATE FUNCTION dbo.itvfBinaryIPv4(@ip AS VARCHAR(15)) RETURNS TABLE AS RETURN ( SELECT CAST( CAST( CAST( PARSENAME( @ip, 4 ) AS INTEGER) AS BINARY(1)) + CAST( CAST( PARSENAME( @ip, 3 ) AS INTEGER) AS BINARY(1)) + CAST( CAST( PARSENAME( @ip, 2 ) AS INTEGER) AS BINARY(1)) + CAST( CAST( PARSENAME( @ip, 1 ) AS INTEGER) AS BINARY(1)) AS BINARY(4)) As bin ) go
这是在这个例子中:
SELECT bin FROM dbo.fnBinaryIPv4('192.65.68.201') --should return 0xC04144C9 go
这里是你如何在INSERT中使用它
INSERT INTo myIpTable SELECT {other_column_values,...}, (SELECT bin FROM dbo.itvfBinaryIPv4('192.65.68.201'))
你可以使用varchar。 IPv4的长度是静态的,但IPv6的长度可能是高度可变的。
除非你有充分的理由把它存储为二进制,否则坚持一个string(文本)types。
这里有一些代码可以将IPV4或IPv6格式转换为二进制(16),然后转换回来。 这是我能想到的最小的forms。 它应该很好的索引,并提供一个相对简单的方法来过滤子网。 需要SQL Server 2005或更高版本。 不确定它是完全防弹的。 希望这可以帮助。
-- SELECT dbo.fn_ConvertIpAddressToBinary('2002:1ff:6c2::1ff:6c2') -- SELECT dbo.fn_ConvertIpAddressToBinary('10.4.46.2') -- SELECT dbo.fn_ConvertIpAddressToBinary('bogus') ALTER FUNCTION dbo.fn_ConvertIpAddressToBinary ( @ipAddress VARCHAR(39) ) RETURNS BINARY(16) AS BEGIN DECLARE @bytes BINARY(16), @vbytes VARBINARY(16), @vbzone VARBINARY(2) , @colIndex TINYINT, @prevColIndex TINYINT, @parts TINYINT, @limit TINYINT , @delim CHAR(1), @token VARCHAR(4), @zone VARCHAR(4) SELECT @delim = '.' , @prevColIndex = 0 , @limit = 4 , @vbytes = 0x , @parts = 0 , @colIndex = CHARINDEX(@delim, @ipAddress) IF @colIndex = 0 BEGIN SELECT @delim = ':' , @limit = 8 , @colIndex = CHARINDEX(@delim, @ipAddress) WHILE @colIndex > 0 SELECT @parts = @parts + 1 , @colIndex = CHARINDEX(@delim, @ipAddress, @colIndex + 1) SET @colIndex = CHARINDEX(@delim, @ipAddress) IF @colIndex = 0 RETURN NULL END SET @ipAddress = @ipAddress + @delim WHILE @colIndex > 0 BEGIN SET @token = SUBSTRING(@ipAddress, @prevColIndex + 1, @Colindex - @prevColIndex - 1) IF @delim = ':' BEGIN SET @zone = RIGHT('0000' + @token, 4) SELECT @vbzone = CAST('' AS XML).value('xs:hexBinary(sql:variable("@zone"))', 'varbinary(2)') , @vbytes = @vbytes + @vbzone IF @token = '' WHILE @parts + 1 < @limit SELECT @vbytes = @vbytes + @vbzone , @parts = @parts + 1 END ELSE BEGIN SET @zone = SUBSTRING('' + master.sys.fn_varbintohexstr(CAST(@token AS TINYINT)), 3, 2) SELECT @vbzone = CAST('' AS XML).value('xs:hexBinary(sql:variable("@zone"))', 'varbinary(1)') , @vbytes = @vbytes + @vbzone END SELECT @prevColIndex = @colIndex , @colIndex = CHARINDEX(@delim, @ipAddress, @colIndex + 1) END SET @bytes = CASE @delim WHEN ':' THEN @vbytes ELSE 0x000000000000000000000000 + @vbytes END RETURN @bytes END
-- SELECT dbo.fn_ConvertBinaryToIpAddress(0x200201FF06C200000000000001FF06C2) -- SELECT dbo.fn_ConvertBinaryToIpAddress(0x0000000000000000000000000A0118FF) ALTER FUNCTION [dbo].[fn_ConvertBinaryToIpAddress] ( @bytes BINARY(16) ) RETURNS VARCHAR(39) AS BEGIN DECLARE @part VARBINARY(2) , @colIndex TINYINT , @ipAddress VARCHAR(39) SET @ipAddress = '' IF SUBSTRING(@bytes, 1, 12) = 0x000000000000000000000000 BEGIN SET @colIndex = 13 WHILE @colIndex <= 16 SELECT @part = SUBSTRING(@bytes, @colIndex, 1) , @ipAddress = @ipAddress + CAST(CAST(@part AS TINYINT) AS VARCHAR(3)) + CASE @colIndex WHEN 16 THEN '' ELSE '.' END , @colIndex = @colIndex + 1 IF @ipAddress = '0.0.0.1' SET @ipAddress = '::1' END ELSE BEGIN SET @colIndex = 1 WHILE @colIndex <= 16 BEGIN SET @part = SUBSTRING(@bytes, @colIndex, 2) SELECT @ipAddress = @ipAddress + CAST('' as xml).value('xs:hexBinary(sql:variable("@part") )', 'varchar(4)') + CASE @colIndex WHEN 15 THEN '' ELSE ':' END , @colIndex = @colIndex + 2 END END RETURN @ipAddress END
正如我想要处理IPv4
和IPv6
,我正在使用VARBINARY(16)
和下面的SQL CLR
函数将text
IP地址显示转换为字节和相反:
[SqlFunction(DataAccess = DataAccessKind.None, IsDeterministic = true)] public static SqlBytes GetIPAddressBytesFromString (SqlString value) { IPAddress IP; if (IPAddress.TryParse(value.Value, out IP)) { return new SqlBytes(IP.GetAddressBytes()); } else { return new SqlBytes(); } } [SqlFunction(DataAccess = DataAccessKind.None, IsDeterministic = true)] public static SqlString GetIPAddressStringFromBytes(SqlBytes value) { string output; if (value.IsNull) { output = ""; } else { IPAddress IP = new IPAddress(value.Value); output = IP.ToString(); } return new SqlString(output); }
sys.dm_exec_connections
在SQL Server 2005 SP1之后使用varchar(48)。 听起来不错,特别是如果你想用它比较你的价值。
实际上,你暂时还不会看到IPv6成为主stream,所以我更喜欢4 tinyint的路线。 说,我使用varchar(48),因为我必须使用sys.dm_exec_connections
…
除此以外。 马克·雷德曼(Mark Redman)的回答提到了以前的辩论问题。
我通常使用普通的旧VARCHAR过滤IPAddress工作正常。
如果你想过滤的IP地址范围,我会把它分成四个整数。
感谢RBarry。 我把一个IP块分配系统放在一起,存储为二进制是唯一的方法。
我将IP块的CIDR表示(例如192.168.1.0/24)存储在varchar字段中,并使用2个计算字段来保存块的开始和结束的二进制forms。 从那里,我可以运行快速查询来查看给定的块是否已经分配或可以自由分配。
我修改你的函数来计算结束IP地址,如下所示:
CREATE FUNCTION dbo.fnDisplayIPv4End(@block AS VARCHAR(18)) RETURNS BINARY(4) AS BEGIN DECLARE @bin AS BINARY(4) DECLARE @ip AS VARCHAR(15) DECLARE @size AS INT SELECT @ip = Left(@block, Len(@block)-3) SELECT @size = Right(@block, 2) SELECT @bin = CAST( CAST( PARSENAME( @ip, 4 ) AS INTEGER) AS BINARY(1)) + CAST( CAST( PARSENAME( @ip, 3 ) AS INTEGER) AS BINARY(1)) + CAST( CAST( PARSENAME( @ip, 2 ) AS INTEGER) AS BINARY(1)) + CAST( CAST( PARSENAME( @ip, 1 ) AS INTEGER) AS BINARY(1)) SELECT @bin = CAST(@bin + POWER(2, 32-@size) AS BINARY(4)) RETURN @bin END; go
对于使用.NET的用户,可以使用IPAddress类来parsingIPv4 / IPv6string,并将其存储为VARBINARY(16)
。 可以使用相同的类将byte[]
转换为string。 如果想要在SQL中转换VARBINARY
:
--SELECT -- dbo.varbinaryToIpString(CAST(0x7F000001 AS VARBINARY(4))) IPv4, -- dbo.varbinaryToIpString(CAST(0x20010DB885A3000000008A2E03707334 AS VARBINARY(16))) IPv6 --ALTER CREATE FUNCTION dbo.varbinaryToIpString ( @varbinaryValue VARBINARY(16) ) RETURNS VARCHAR(39) AS BEGIN IF @varbinaryValue IS NULL RETURN NULL IF DATALENGTH(@varbinaryValue) = 4 BEGIN RETURN CONVERT(VARCHAR(3), CONVERT(INT, SUBSTRING(@varbinaryValue, 1, 1))) + '.' + CONVERT(VARCHAR(3), CONVERT(INT, SUBSTRING(@varbinaryValue, 2, 1))) + '.' + CONVERT(VARCHAR(3), CONVERT(INT, SUBSTRING(@varbinaryValue, 3, 1))) + '.' + CONVERT(VARCHAR(3), CONVERT(INT, SUBSTRING(@varbinaryValue, 4, 1))) END IF DATALENGTH(@varbinaryValue) = 16 BEGIN RETURN sys.fn_varbintohexsubstring(0, @varbinaryValue, 1, 2) + ':' + sys.fn_varbintohexsubstring(0, @varbinaryValue, 3, 2) + ':' + sys.fn_varbintohexsubstring(0, @varbinaryValue, 5, 2) + ':' + sys.fn_varbintohexsubstring(0, @varbinaryValue, 7, 2) + ':' + sys.fn_varbintohexsubstring(0, @varbinaryValue, 9, 2) + ':' + sys.fn_varbintohexsubstring(0, @varbinaryValue, 11, 2) + ':' + sys.fn_varbintohexsubstring(0, @varbinaryValue, 13, 2) + ':' + sys.fn_varbintohexsubstring(0, @varbinaryValue, 15, 2) END RETURN 'Invalid' END
我正在使用Varchar(15)到目前为止,一切都为我工作。 插入,更新,select。 我刚开始有一个IP地址的应用程序,但我还没有做很多的开发工作。
这里是select语句:
select*从dbo.Server其中[IP] =('132.46.151.181')去
我喜欢SandRock的function。 但是我在dbo.fn_ConvertIpAddressToBinary的代码中发现了一个错误。 @ipAddress VARCHAR(39)的传入参数在将@delim连接到它时太小。
SET @ipAddress = @ipAddress + @delim
你可以将它增加到40.或者更好的方法是使用一个更大的新variables,并在内部使用它。 这样你就不会失去大量的最后一对。
SELECT dbo.fn_ConvertIpAddressToBinary('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff')