Какой тип данных следует выбрать для хранения IP-адреса на SQL Server?
Выбрав правильный тип данных, будет достаточно легко фильтровать по IP-адресу?
Какой тип данных следует выбрать для хранения IP-адреса на SQL Server?
Выбрав правильный тип данных, будет достаточно легко фильтровать по IP-адресу?
Технически правильный способ хранения IPv4 - это двоичный файл (4), поскольку это то, чем он является на самом деле (нет, даже не INT32/INT (4), числовая текстовая форма, которую мы все знаем и любим (255.255.255.255), является просто отображение преобразования его двоичного содержимого).
Если вы сделаете это таким образом, вам понадобится преобразовать функции в формат текстового отображения:
Вот как преобразовать текстовую форму отображения в двоичный файл:
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.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
 Наконец, при поиске и сравнении всегда используйте двоичную форму, если вы хотите использовать свои индексы.
ОБНОВИТЬ:
Я хотел бы добавить, что одним из способов решения проблем, связанных с производительностью скалярных пользовательских функций в SQL Server, но все же сохранить повторное использование кода для функции, является использование вместо этого iTVF (встроенная табличная функция). Вот как первая функция выше (строка в двоичную) может быть переписана как 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 INTo myIpTable
SELECT {other_column_values,...},
       (SELECT bin FROM dbo.itvfBinaryIPv4('192.65.68.201'))
		Вы можете использовать varchar. Длина IPv4 является статической, но IPv6 может быть сильно переменной.
Если у вас нет оснований хранить его как двоичный, придерживайтесь строкового (текстового) типа.
Вот код для преобразования IPV4 или IPv6 в формате varchar в двоичный (16) и обратно. Это наименьшая форма, о которой я мог думать. Он должен хорошо индексировать и обеспечивать относительно простой способ фильтрации на подсетях. Требуется 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 для преобразования представления IP-адреса text в байты и наоборот:
[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 использует varchar (48) после SQL Server 2005 с пакетом обновления 1 (SP1). Звучит достаточно хорошо для меня, особенно если вы хотите использовать его по сравнению с вашим значением.
Реально, вы еще не увидите IPv6 в качестве основного потока, поэтому я предпочел бы 4 маршрута tinyint. Говоря это, я использую varchar (48), потому что мне нужно использовать sys.dm_exec_connections...
В противном случае. Ответ Марка Редмана упоминает предыдущий вопрос SO  debate.
Для пользователей, использующих .NET, можно использовать класс IPAddress для разбора строки IPv4/IPv6 и сохранить его как VARBINARY(16). Может использовать тот же класс для преобразования byte[] в строку. Если вы хотите преобразовать VARBINARY в SQL:
--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 для IPAddress, отлично работает.
Если вы хотите фильтровать диапазоны IP-адресов, я бы разбил их на четыре целых числа.
Спасибо, Рэйарри. Я собираю систему распределения блоков IP, и сохранение в качестве двоичного файла является единственным способом.
Я сохраняю представление CIDR (например: 192.168.1.0/24) IP-блока в поле varchar и используя 2 вычисленных поля для хранения двоичной формы начала и конца блока. Оттуда я могу запустить быстрые запросы, чтобы увидеть, был ли данный блок уже выделен или может быть назначен.
Я изменил вашу функцию для вычисления конечного 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, [email protected]) AS BINARY(4))
    RETURN @bin
END;
go
		Мне нравятся функции SandRock. Но я обнаружил ошибку в коде dbo.fn_ConvertIpAddressToBinary. Приходящий параметр @ipAddress VARCHAR (39) слишком мал, когда вы присоединяете @delim к нему.
SET @ipAddress = @ipAddress + @delim
Вы можете увеличить его до 40. Или еще лучше использовать новую переменную, которая больше, и использовать ее внутри. Таким образом, вы не потеряете последнюю пару на больших количествах.
SELECT dbo.fn_ConvertIpAddressToBinary('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff')
		 Я использую varchar(15) пока у меня все работает. Вставить, Обновить, Выбрать. Я только что запустил приложение с IP-адресами, хотя пока еще не проделал большую работу по разработке.
Вот утверждение выбора:
select * From dbo.Server 
where  [IP] = ('132.46.151.181')
Go