我们最近开始使用MaxMind Geolite数据库作为基于IP的城市查找。有很多指令可以将数据导入SQL Server(我已经完成了)。现在我需要弄清楚如何在子网内搜索给定的IP。
DB Schema:
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
);
'network'列包含带有IP /子网的行数据(例如:1.0.32.0 / 19,1.0.64.0 / 20,1.0.80.0 / 22)
给定一个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等功能。
谢谢!
答案 0 :(得分: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的按位AND,你最终会得到:
00000001 00000000 00100000 00000000
...转换为点状四边形为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, 0xfffffe00),
(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子网,一般方法是相同的,但字符串转换和算术会更复杂。