129 votes

Type de données pour stocker l'adresse IP dans le serveur SQL

Quel type de données dois-je choisir pour stocker une adresse IP dans un serveur SQL ?

En sélectionnant le bon type de données, il serait assez facile de filtrer par adresse IP ?

0 votes

Jetez un coup d'œil à cette réponse : stackoverflow.com/questions/1038950/

141voto

RBarryYoung Points 23349

La façon techniquement correcte de stocker IPv4 est binaire(4), puisque c'est ce qu'il est réellement (non, pas même un INT32/INT(4), la forme numérique textuelle que nous connaissons et aimons tous (255.255.255.255) n'étant que la conversion d'affichage de son contenu binaire).

Si vous procédez de cette manière, vous aurez besoin de fonctions pour convertir le format d'affichage textuel :

Voici comment convertir la forme d'affichage textuelle en binaire :

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

Et voici comment reconvertir le binaire en forme d'affichage textuel :

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

Voici une démonstration de leur utilisation :

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

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

Enfin, lorsque vous effectuez des recherches et des comparaisons, utilisez toujours la forme binaire si vous voulez être en mesure de tirer parti de vos index.


UPDATE :

Je voulais ajouter qu'une façon de résoudre les problèmes de performance inhérents aux UDF scalaires dans SQL Server, tout en conservant la réutilisation du code d'une fonction, est d'utiliser une iTVF (inline table-valued function) à la place. Voici comment la première fonction ci-dessus (string to binary) peut être réécrite comme une 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

Le voici dans l'exemple :

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

Et voici comment vous l'utiliseriez dans un INSERT

INSERT INTo myIpTable
SELECT {other_column_values,...},
       (SELECT bin FROM dbo.itvfBinaryIPv4('192.65.68.201'))

45 votes

Je pense que cela n'est correct que dans un sens académique. Sans connaître le but et le problème du domaine que le posteur essaie de résoudre, je soupçonne que cela compliquera inutilement l'interaction avec les données et dégradera potentiellement les performances.

22 votes

IPv4 est une séquence ordonnée de quatre octets. C'est IS c'est son domaine, et dans le format de stockage, c'est un BIN(4). Le format de stockage n'aura pas d'incidence sur les performances car il s'agit du format optimal. La fonction de conversion pourrait l'être (parce que les udf sont nuls sur le serveur SQL), ce qui peut être résolu soit par l'intégration en ligne, soit en effectuant la conversion sur le client. Enfin, cette approche présente l'avantage considérable de pouvoir rechercher des adresses dans des sous-réseaux de classe 1, 2 ou 3 à l'aide de balayages de plages indexés (WHERE ip BETWEEN fnBinaryIPv4('132.31.55.00') AND fnBinaryIPv4('132.31.55.255')).

1 votes

@RBarryYoung Je le stocke en tant qu'entier. Pourriez-vous expliquer quel est l'avantage en termes de performance de le stocker en tant que binaire ?

36voto

Vous pouvez utiliser varchar. La longueur d'IPv4 est statique, mais celle d'IPv6 peut être très variable.

À moins que vous n'ayez une bonne raison de les stocker sous forme binaire, restez-en à un type de chaîne (textuel).

50 votes

La longueur de l'IPv6 est très fixe - 128 bits.

6 votes

À moins que vous ne parliez de données qu'un humain ne lira jamais ou d'une quantité massive de données, c'est la meilleure réponse.

12 votes

Une raison simple d'utiliser des binaires et non des chaînes de caractères : La version binaire permet vérification des plages numériques des adresses IP ! La version texte ne le fait pas. Cela dépend bien sûr de l'utilisation requise, mais les nombres binaires sont plus utiles car ils ont une signification réelle.

20voto

Jerry Birchler Points 41

Voici un code pour convertir l'IPV4 ou l'IPv6 au format varchar en binaire(16) et inversement. C'est la plus petite forme à laquelle j'ai pensé. Elle devrait bien s'indexer et fournir un moyen relativement facile de filtrer sur les sous-réseaux. Requiert SQL Server 2005 ou une version ultérieure. Je ne suis pas sûr qu'il soit totalement à l'épreuve des balles. J'espère que cela vous aidera.

-- 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

0 votes

Cette réponse a fonctionné sans problème pour la base de données db-ip IP to country. Une conversion aller-retour n'a montré que des différences mineures lorsque les 0 ont été supprimés de ipv6 (en tête et en queue).

1 votes

Dans ToBinary(), on a rencontré quelques problèmes avec le plan de requête et l'utilisation de fn_varbintohexstr() qui n'est pas marqué comme déterministe. Que diriez-vous pour la section else '.' : select @ vbzone = convert(varbinary(2), convert(tinyint, @ token)) ? Cela semble équivalent. Pas besoin de @ zone ou de moteur xml ? Il semblerait que l'on puisse obtenir un gain de vitesse appréciable si le moteur xml supprimait aussi d'une manière ou d'une autre les " :".

0 votes

Concat_ws('.',(IPAddr & 0xFF000000)>>24,(IPAddr & 0xFF0000)>>16,(IPAddr & 0xFF00)>>8, (IPAddr & 0xFF)) convertira un long non signé contenant une adresse IP en une forme lisible par l'homme.

13voto

gotqn Points 4247

Comme je veux gérer les deux IPv4 et IPv6 J'utilise VARBINARY(16) et ce qui suit SQL CLR pour convertir les text Présentation des adresses IP en octets et inversement :

[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);
}

8voto

M. Turnhout Points 91

Les utilisateurs de .NET peuvent utiliser la classe IPAddress pour analyser une chaîne IPv4/IPv6 et la stocker sous la forme d'un fichier de type VARBINARY(16) . On peut utiliser la même classe pour convertir byte[] en chaîne de caractères. Si vous voulez convertir le VARBINARY en 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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X