【问题标题】:SQL Server join IP address to IP rangeSQL Server 将 IP 地址连接到 IP 范围
【发布时间】:2013-07-29 11:01:29
【问题描述】:

在 SQL Server 中,加入 IPv4 地址表的最简单方法是什么...

IPAddress
------------
10.70.80.34
10.70.81.60
10.70.81.205

以 CIDR 表示法的 IP 范围表...

IPRange          Description
---------------  -----------
10.70.80.0/24    Sydney
10.70.81.0/25    Melbourne
10.70.81.128/25  Perth

IP 范围表中的行数将少于 100 行。

【问题讨论】:

  • 我假设您的 IPAddress 和 IPRange 列是 String
  • 是的,两者都是 nvarchar。

标签: sql sql-server sql-server-2008


【解决方案1】:

ParseName() 函数在这种情况下可能对您有用

DECLARE @ips table (
   ip_address varchar(15)
);

INSERT INTO @ips (ip_address)
  VALUES ('10.70.80.34')
       , ('10.70.81.60')
       , ('10.70.81.205');

SELECT ip_address
     , ParseName(ip_address, 4) As first_octet
     , ParseName(ip_address, 3) As second_octet
     , ParseName(ip_address, 2) As third_octet
     , ParseName(ip_address, 1) As fourth_octet
FROM   @ips

结果

ip_address      first_octet   second_octet   third_octet  fourth_octet
--------------- ------------- -------------- ------------ -------------
10.70.80.34     10            70             80           34
10.70.81.60     10            70             81           60
10.70.81.205    10            70             81           205

【讨论】:

    【解决方案2】:

    我编写了以下函数,该函数适用于少量行。对于较大的表,IP 应存储在in binary form。

    CREATE FUNCTION IPAddressInRange
    (
        @IPAddress NVARCHAR(MAX),
        @IPRange NVARCHAR(MAX)
    )
    RETURNS BIT AS
    BEGIN
        DECLARE @SlashPos INT = CHARINDEX('/', @IPRange);
        DECLARE @Network NVARCHAR(MAX) = SUBSTRING(@IPRange, 1, @SlashPos - 1);
        DECLARE @PrefixBits INT = CAST(SUBSTRING(@IPRange, @SlashPos + 1, 2) AS INT);
    
        DECLARE @IPAddressInt BIGINT =
            PARSENAME(@IPAddress, 4) * POWER(CAST(2 AS BIGINT), 24) +
            PARSENAME(@IPAddress, 3) * POWER(CAST(2 AS BIGINT), 16) +
            PARSENAME(@IPAddress, 2) * POWER(CAST(2 AS BIGINT), 8) +
            PARSENAME(@IPAddress, 1);
    
        DECLARE @NetworkInt BIGINT =
            PARSENAME(@Network, 4) * POWER(CAST(2 AS BIGINT), 24) +
            PARSENAME(@Network, 3) * POWER(CAST(2 AS BIGINT), 16) +
            PARSENAME(@Network, 2) * POWER(CAST(2 AS BIGINT), 8) +
            PARSENAME(@Network, 1);
    
        DECLARE @Mask BIGINT = POWER(CAST(2 AS BIGINT), 32) -
            POWER(CAST(2 AS BIGINT), 32 - @PrefixBits);
    
        RETURN CASE WHEN @IPAddressInt & @Mask = @NetworkInt THEN 1 ELSE 0 END;
    END
    

    示例用法:

    SELECT *
    FROM IPAddressTable a
    JOIN IPRangeTable r
    ON dbo.IPAddressInRange(a.IPAddress, r.IPRange) = 1
    

    【讨论】:

      【解决方案3】:

      与使用任何其他语言的方式相同 - 使用一堆拼写出的逻辑:

      1. 将字符串转换为 4 个整数
      2. 确定位掩码(网络掩码)
      3. 在应用(按位and)掩码后,从左到右一次比较一个数字

      SQL Server 唯一的特殊之处是您希望避免使用函数(内联表值函数除外)。您还需要考虑到 SQL Server 在处理字符串时速度很慢。

      如果值存储为字符串,我建议编写一个 CLR 函数。在 .NET 代码中,这实现起来相当简单,并且 CLR 标量值函数的调用开销并不大。但是,您将无法在任一表上使用索引。

      如果 IPRange 是一个普通的查找表,你可能会没事。但是,如果您要编写返回所有属于特定范围的 IP 地址的查询,这可能会成为一个问题。在这种情况下,我建议将 IP 地址的四个整数以及掩码的整数存储在单独的字段中。这样索引是可能的,并且连接逻辑变成了一个相当简单的表达式。

      【讨论】:

        【解决方案4】:

        如果你使用 PostgreSQL,还有一个更简单的方法:

        CREATE OR REPLACE FUNCTION IPInRange(VARCHAR(16), VARCHAR(20))
        RETURNS BOOLEAN AS $$
            SELECT CAST($1 AS inet) << CAST($2 AS inet);
        $$ LANGUAGE SQL;
        

        【讨论】:

          猜你喜欢
          • 2013-07-18
          • 1970-01-01
          • 2011-02-11
          • 1970-01-01
          • 2022-07-22
          • 1970-01-01
          • 1970-01-01
          • 2020-10-07
          • 2017-11-27
          相关资源
          最近更新 更多