Convert IP to 32-bit Binary in TSQL [duplicate]

ε祈祈猫儿з 提交于 2019-12-12 09:29:14

问题


I have the following stored procedure that converts IPs to 32-bit binary in TSQL. It works but is EXTREMELY slow (has only converted 8,400 in 40 minutes) - probably because it uses cursors. Does anyone have suggestions and/or a different approach to improve performance?

Here's an example: 1.1.79.129 is converted to 00000001.00000001.01001111.10000001

Thanks

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   

回答1:


Well it looks like IPV4, so I am going to go on that assumption. You are also converting to some really long binary text representation, I recommend that you just use BINARY(4) instead, and my answer will assume that. You can convert this your 131-character text representation if you want, but I don't know why you would want to.

I posted an answer to a similar general question some years ago here: https://stackoverflow.com/a/1385701/109122

That basically showed this function:

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

Today however, I would recommend making it an inline Table-Value Function instead for performance.

This is how you could do that:

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

And this is how you could use that to perform your conversion and update without a cursor:

;
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 

This should be very fast.




回答2:


A function can be used in an UPDATE so you don't need to use a cursor. Conversion to a binary string, if that's really what you need, is left as an exercise.

How about this function to convert an IPV4 string to a 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

It returns a BIGINT to avoid fiddling about with negative values.

Going from a BIGINT to a VARCHAR with zero-padding I use:

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



回答3:


For those who are looking for one time IP4 to bigint conversion rather then performance issues with existing stored procedures. Below is the sample inline code that makes conversion

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


来源:https://stackoverflow.com/questions/20429578/convert-ip-to-32-bit-binary-in-tsql

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!