2017-05-09 96 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