2017-05-09 87 views
1

我们最近开始使用MaxMind Geolite数据库作为基于IP的城市查询。有很多关于如何将数据导入SQL Server的说明(我已经完成)。现在我需要弄清楚如何在子网内搜索给定的IP。用于在子网中查找IP地址的SQL查询

DB模式:

CREATE TABLE GeoIP ( 
    network varchar(20) not null, 
    geoname_id varchar(20) not null, 
    registered_country_geoname_id varchar(20) not null, 
    represented_country_geoname_id varchar(20) not null, 
    is_anonymous_proxy int, 
    is_satellite_provider int, 
    postal_code varchar(20), 
    latitude Decimal(9,6), 
    longitude Decimal(9,6), 
    accuracy_radius int 
); 

'网络' 列具有与IP /子网行数据:

(例如1.0.32.0/19,1.0.64.0/20,1.0.80.0/22)

给定一个IP地址,我试图编写一个SELECT语句来返回geoname_id。

Ex: SELECT geoname_id FROM GeoIP where @user_ip in {some expression} 

我想做到这一点,而无需网络列爆炸了成“low_ip”和“high_ip” BIGINT列。但是,如果这是唯一的方法,那么我也可以使用一些帮助来编写全局UPDATE语句来添加现有数据中的那些列。

需要SQL SERVER 2008的所以不能使用任何酷的Postgres等功能。

谢谢!

回答

1

从您的网络列中,您已经可以看到网络掩码中的位数,并且借助一点点算术运算,可以轻松检测用户ip是否落入该网络。因此,我建议你将该列分成它的(二进制)网络IP和它的cidr号码。

让我解释一下。如果我们按照您提供的第一个示例(10.0.32.0/19),我们可以看到它的网络掩码(“/ 19”位)以二进制表示为19个,其他所有位都设置为零:

11111111 11111111 11100000 00000000 

让我们的1.0.32.56样本用户IP:

00000001 00000000 00100000 00111000 

你可以看到,如果你采取的按位和/ 19网络掩码与用户IP一起,你会结束:

00000001 00000000 00100000 00000000 

...它转换为虚线的点ds为1.0.32.0。看起来熟悉?

无论如何,这是我为你的问题采取的方法。首先,我们需要使用udf将IP地址转换为二进制。我无耻地窃取this answer之一:

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 TABLE netmask (
    bits TINYINT PRIMARY KEY, 
    binary_mask BINARY(4) NOT NULL 
) 

INSERT INTO netmask (bits, binary_mask) VALUES 
    (0, 0x00000000), (1, 0x80000000), (2, 0xc0000000), (3, 0xe0000000), 
    (4, 0xf0000000), (5, 0xf8000000), (6, 0xfc000000), (7, 0xfe000000), 
    (8, 0xff000000), (9, 0xff800000), (10, 0xffc00000), (11, 0xffe00000), 
    (12, 0xfff00000), (13, 0xfff80000), (14, 0xfffc0000), (15, 0xfffe0000), 
    (16, 0xffff0000), (17, 0xffff8000), (18, 0xffffc000), (19, 0xffffe000), 
    (20, 0xfffff000), (21, 0xfffff800), (22, 0xfffffc00), (23, 0xfffffc00), 
    (24, 0xffffff00), (25, 0xffffff80), (26, 0xffffffc0), (27, 0xffffffe0), 
    (28, 0xfffffff0), (29, 0xfffffff8), (30, 0xfffffffc), (31, 0xfffffffe), 
    (32, 0xffffffff) 

接下来我们创建了两个新列和填充其中:

ALTER TABLE GeoIP 
ADD binary_network BINARY(4), network_bits TINYINT 
GO 

UPDATE GeoIP 
SET binary_network = dbo.fnBinaryIPv4(SUBSTRING(network, 0, PATINDEX('%/%', network))), 
    network_bits = CAST(SUBSTRING(network, PATINDEX('%/%', network) + 1, 3) AS TINYINT) 

所以现在我们可以重写查询为:

DECLARE @binary_user_ip BIGINT 
SELECT @binary_user_ip = dbo.fnBinaryIPv4(@user_ip) 

SELECT geoname_id 
FROM GeoIP g 
    JOIN netmask n ON g.network_bits = n.bits 
WHERE @binary_user_ip & n.binary_mask = g.binary_network 

注 - 这只适用于IPv4。如果你想检测IPv6子网,一般的方法是一样的,但字符串转换和算术会更复杂。

+0

这很好。你能解释这条线是什么吗? (其中@binary_user_ip&n.binary_mask = g.binary_network)。 –

+0

另外,如果我使用binary_network作为我的主要搜索列,我应该索引该列吗? –

+0

该行是按位与。与我之前展示过的二进制例子一样。是的,如果binary_network是你的主要搜索栏,你几乎肯定应该为它编制索引。 – duckbenny