views:

2239

answers:

6

What datatype should i choose for storing an Ip Address in a SQL Server?

By selecting the right datatype would it be easy enough to filter by IP address then?

+5  A: 

You can use varchar. The length of IPv4 is static, but that of IPv6 may be highly variable.

Unless you have a goog reason to store it as binary, stick with textual type.

Developer Art
+2  A: 

I usually use a plain old VARCHAR filtering for an IPAddress works fine.

If you want to filter on ranges of IP address I'd break it into four integers.

Kindness,

Dan

Daniel Elliott
What value does nvarchar add except doubling the size?
gbn
Good point, inserted more coffee and editted ;)
Daniel Elliott
+5  A: 

Have a look at this answer:

http://stackoverflow.com/questions/1038950/what-is-be-the-most-appropriate-data-type-for-storing-an-ip-address-in-sql-server

Mark Redman
+1.. it's been discussed intensively before
gbn
+1  A: 

sys.dm_exec_connections uses varchar(48) after SQL Server 2005 SP1. Sounds good enough for me especially if you want to use it compare to your value.

Realistically, you won't see IPv6 as mainstream for a while yet, so I'd prefer the 4 tinyint route. Saying that, I'm using varchar(48) because I have to use sys.dm_exec_connections...

Otherwise. Mark Redman's answer mentions a previous SO debate question.

gbn
+5  A: 

The technically correct way to store IPv4 is Binary(4), since that is what it actually is (no, not even an INT32/INT(4)), the numeric textual form that we all know and love (255.255.255.255) being just the display conversion of it's binary content.

If you do it this way, you will want functions to convert to and from the textual-display format:

Here's how to convert the textual display form to binary:

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

And here's how to convert the binary back to the textual display form:

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

Here's a demo of how to use them:

SELECT dbo.fnBinaryIPv4('192.65.68.201')
--should return 0xC04144C9
go

SELECT dbo.fnDisplayIPv4( 0xC04144C9 )
-- should return '192.65.68.201'
go

Finally, when doing lookups and compares, always use the binary form if you want to be able to leverage your indexes.

RBarryYoung
I think this is only correct in an academic sense. Without knowing the purpose and domain problem for which the poster is trying to solve I suspect this will unnecessarily complicate interacting with the data and potentially degrade performance.
esabine
IPv4 is an ordered sequence of four bytes. That *IS* it's domain, and in storage format that's a BIN(4). The storage format will not interfere with performance because it's the optimal format. The conversion function might (because udf's suck on SQL server), which can be solved either by in-lineing or doing the conversion on the client. Finally, this approach has the significant advantage that it can search for addresses in Class 1,2, or 3 subnetworks using indexed range scans (WHERE ip BETWEEN fnBinaryIPv4('132.31.55.00') AND fnBinaryIPv4('132.31.55.255') )
RBarryYoung
A: 

Thanks RBarry. I'm putting together an IP block allocation system and storing as binary is the only way to go.

I'm storing the CIDR representation (ex: 192.168.1.0/24) of the IP block in a varchar field, and using 2 calculated fields to hold the binary form of the start and end of the block. From there, I can run fast queries to see if a given block as already been allocated or is free to assign.

I modified your function to calculate the ending IP Address like so:

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