我有以下存储过程,在TSQL中将IP转换为32位二进制文件。它工作但很慢(在40分钟内只转换了8,400) - 可能是因为它使用了游标。有没有人有建议和/或不同的方法来提高绩效?
以下是一个例子:1.1.79.129转换为00000001.00000001.01001111.10000001
由于
CREATE PROCEDURE [dbo].[sp_UpdateTableConvertIPToBinary]
AS
SET NOCOUNT ON
declare @IP nvarchar(255)
declare IPList cursor for
/*
Get IP address from CIDR Block where Binary has not been assigned
*/
select left(IP,charindex('/',IP)-1) as Block from MyDB.dbo.MyTable
WHERE IP IS NOT NULL AND [Binary] IS NULL
ORDER BY Block
OPEN IPList
FETCH NEXT FROM IPList
INTO @IP
WHILE @@FETCH_STATUS = 0
BEGIN
begin
declare @Octet as varchar(15)
declare @Div as integer
declare @Output as varchar(100)
declare @n as integer
declare @OriginalIP varchar(15)
select @OriginalIP = @IP
select @Div = '128'
select @Output = ''
select @n = 0
WHILE @n < 4
begin
/*
Set @Octet = Class to the left of the first '.' in @IP
If no '.' in @IP, set @Octet = entire @IP (this will happen for the last Class)
*/
IF CHARINDEX('.',@IP) > 0
begin
select @Octet = left(@IP,charindex('.',@IP)-1)
end
else
begin
select @Octet = @IP
end
/*
If @Octet >= 128, append 1 to @Output and subtract 128 from @Octet
If not, append 0 to @Output
*/
if cast(@Octet as int) >= @Div
begin
select @Output = @Output + '1'
select @Octet = cast(@Octet as int) - @Div
end
else
begin
select @Output = @Output + '0'
end
/*
If @Octet >= 64, append 1 to @Output and subtract 64 from @Octet
If not, append 0 to @Output
*/
if cast(@Octet as int) >= (@Div/2)
begin
select @Output = @Output + '1'
select @Octet = cast(@Octet as int) - (@Div/2)
end
else
begin
select @Output = @Output + '0'
end
/*
If @Octet >= 32, append 1 to @Output and subtract 32 from @Octet
If not, append 0 to @Output
*/
if cast(@Octet as int) >= (@Div/4)
begin
select @Output = @Output + '1'
select @Octet = cast(@Octet as int) - (@Div/4)
end
else
begin
select @Output = @Output + '0'
end
/*
If @Octet >= 16, append 1 to @Output and subtract 16 from @Octet
If not, append 0 to @Output
*/
if cast(@Octet as int) >= (@Div/8)
begin
select @Output = @Output + '1'
select @Octet = cast(@Octet as int) - (@Div/8)
end
else
begin
select @Output = @Output + '0'
end
/*
If @Octet >= 8, append 1 to @Output and subtract 8 from @Octet
If not, append 0 to @Output
*/
if cast(@Octet as int) >= (@Div/16)
begin
select @Output = @Output + '1'
select @Octet = cast(@Octet as int) - (@Div/16)
end
else
begin
select @Output = @Output + '0'
end
/*
If @Octet >= 4, append 1 to @Output and subtract 4 from @Octet
If not, append 0 to @Output
*/
if cast(@Octet as int) >= (@Div/32)
begin
select @Output = @Output + '1'
select @Octet = cast(@Octet as int) - (@Div/32)
end
else
begin
select @Output = @Output + '0'
end
/*
If @Octet >= 2, append 1 to @Output and subtract 2 from @Octet
If not, append 0 to @Output
*/
if cast(@Octet as int) >= (@Div/64)
begin
select @Output = @Output + '1'
select @Octet = cast(@Octet as int) - (@Div/64)
end
else
begin
select @Output = @Output + '0'
end
/*
If @Octet >= 1, append 1 to @Output and subtract 1 from @Octet
If not, append 0 to @Output
*/
if cast(@Octet as int) >= (@Div/128)
begin
select @Output = @Output + '1'
select @Octet = cast(@Octet as int) - (@Div/128)
end
else
begin
select @Output = @Output + '0'
end
/*
if @n < 3, append . to @Output
*/
if @n < 3
begin
select @Output = @Output + '.'
end
/*
Remove the Octet just converted to Binary from @IP and increment the counter
*/
select @IP = right(@IP,len(@IP) - charindex('.',@IP))
select @n = @n+1
end
/*
Update table, set Binary = @Output
*/
UPDATE MyDB.dbo.MyTable Set Binary = @Output WHERE left(IP,charindex('/',IP)-1) = @OriginalIP
end
FETCH NEXT FROM IPList
INTO @IP
END
CLOSE IPList
DEALLOCATE IPList
答案 0 :(得分:0)
好吧它看起来像IPV4,所以我将继续这个假设。您也转换为一些非常长的二进制文本表示,我建议您只使用BINARY(4)
,我的答案将假设。如果需要,您可以将这个131字符的文本表示转换为此,但我不知道您为什么要这样做。
几年前我在这里发布了一个类似的一般问题的答案:https://stackoverflow.com/a/1385701/109122
这基本上显示了这个功能:
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
今天,我建议将其作为内联表值函数,而不是性能。
这就是你如何做到的:
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
这就是您可以使用它来执行转换和更新而无需光标:
;
WITH cte As
(
SELECT *
FROM MyDB.dbo.MyTable
OUTER APPLY dbo.itvfBinaryIPv4(left(IP,charindex('/',IP)-1))
)
UPDATE cte
Set Binary = bin
WHERE IP IS NOT NULL
AND [Binary] IS NULL
这应该非常快。
答案 1 :(得分:-1)
可以在UPDATE中使用函数,因此您不需要使用游标。转换为二进制字符串,如果这正是您所需要的,则留作练习。
将IPV4字符串转换为BIGINT的函数怎么样:
create function [dbo].[IntegerIPV4Address]( @IPV4Address VarChar(16) )
returns BigInt
with SchemaBinding -- Deterministic function.
begin
-- NB: ParseName is non-deterministic.
declare @Dot1 as Int = CharIndex( '.', @IPV4Address );
declare @Dot2 as Int = CharIndex( '.', @IPV4Address, @Dot1 + 1 );
declare @Dot3 as Int = CharIndex( '.', @IPV4Address, @Dot2 + 1 );
return Cast( Substring( @IPV4Address, 0, @Dot1 ) as BigInt ) * 0x1000000 +
Cast( Substring( @IPV4Address, @Dot1 + 1, @Dot2 - @Dot1 - 1 ) as BigInt ) * 0x10000 +
Cast( Substring( @IPV4Address, @Dot2 + 1, @Dot3 - @Dot2 - 1 ) as BigInt ) * 0x100 +
Cast( Substring( @IPV4Address, @Dot3 + 1, Len( @IPV4Address ) * 1 ) as BigInt );
end
它返回一个BIGINT以避免摆弄负值。
使用零填充从BIGINT转到VARCHAR我使用:
create function [dbo].[NormalizedIPV4Address]( @IntegerIPV4Address as BigInt )
returns VarChar(16)
with SchemaBinding -- Deterministic function.
begin
declare @BinaryAddress as VarBinary(4) = Cast( @IntegerIPV4Address as VarBinary(4) );
return Right( '00' + Cast( Cast( Substring( @BinaryAddress, 1, 1 ) as Int ) as VarChar(3) ), 3 ) +
'.' + Right( '00' + Cast( Cast( Substring( @BinaryAddress, 2, 1 ) as Int ) as VarChar(3) ), 3 ) +
'.' + Right( '00' + Cast( Cast( Substring( @BinaryAddress, 3, 1 ) as Int ) as VarChar(3) ), 3 ) +
'.' + Right( '00' + Cast( Cast( Substring( @BinaryAddress, 4, 1 ) as Int ) as VarChar(3) ), 3 )
end
答案 2 :(得分:-1)
对于那些正在寻找IP4到bigint转换而不是现有存储过程的性能问题的人。以下是进行转换的示例内联代码
SELECT CONVERT(bigint, LEFT([IP], CHARINDEX('.', [IP]) - 1)) +
CONVERT(bigint, SUBSTRING([IP],
CHARINDEX('.', [IP]) + 1,
CHARINDEX('.', [IP], CHARINDEX('.', [IP]) + 1)
- CHARINDEX('.', [IP]) - 1)) * 256 +
CONVERT(bigint, SUBSTRING([IP],
CHARINDEX('.', [IP], CHARINDEX('.', [IP]) + 1) + 1,
CHARINDEX('.', [IP], CHARINDEX('.', [IP], CHARINDEX('.', [IP]) + 1) + 1)
- CHARINDEX('.', [IP], CHARINDEX('.', [IP]) + 1) - 1)) * 65536 +
CONVERT(bigint, RIGHT([IP],
LEN([IP]) - 1
- CHARINDEX('.', [IP], CHARINDEX('.', [IP], CHARINDEX('.', [IP]) + 1) + 1) + 1)) * 16777216
AS Value
FROM IPAddresses
-- additional check if IPv6 addresses are in the table
WHERE CHARINDEX(':', [IP]) = 0