2017-05-09 14 views
1

最近MaxMind GeoliteデータベースをIPベースの都市の検索として使用し始めました。 SQL Serverにデータを取得する方法はたくさんあります(これまで行ってきました)。今私はどのようにサブネット内の特定のIPを検索するかを理解する必要があります。サブネットでIPアドレスを検索するSQL

DBスキーマ:(:1.0.32.0/19、1.0.64.0/20、1.0.80.0/22 EX)

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 /サブネットと行データを有します

単一のIPアドレスが与えられたので、私はgeoname_idを返すSELECTステートメントを作成しようとしています。

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

私は「low_ip」と「high_ip」BIGINT列にネットワーク列を爆発することなく、これを行うにしたいと思います。しかし、これが唯一の方法であれば、既存のデータからそれらの列を追加するためのグローバルなUPDATE文を書く方法についていくつかの助けをすることもできます。

SQL Server 2008ではこれが必要なので、すばらしいPostgresなどの機能は使用できません。

ありがとうございます!

答えて

1

ネットワークの列から、ネットマスク内のビット数を確認できます。少しのビット単位の計算によって、ユーザーのIPがそのネットワーク内にあるかどうかを簡単に検出できます。したがって、私はあなたがその(2進)ネットワーク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 

... dotted quaに変換するdsを1.0.32.0とする。見覚えがあります?

とにかく、あなたの問題に取り組んでいます。まず、udfを使ってIPアドレスをバイナリに変換する必要があります。私は臆面もなくthis answerから1を盗んだ:

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) 

次の我々は我々の2つの新しい列を作成し、移入しますそれら:

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

その行はビット単位のANDです。私が前に示したバイナリの例と同じことです。そして、ええ、もしbinary_networkがあなたの主な検索カラムであれば、それはほぼ確実に索引付けするべきです。 – duckbenny

関連する問題