1

在 SQL (postgresql 8.4.x) 中,如何有效地计算可能包含网络块的最小COUNT网络块内的 IP 记录数?例如,我不想同时计算and 。10.0.0.110/80/0

更具体地说,给定:

-- CREATE TABLE iplog (ip INET NOT NULL, ...)
--
      ip      | ...
==============+=====
192.168.1.100 | ...
192.168.1.101 | ...
192.168.55.5  | ...
10.1.2.3      | ...

-- CREATE TABLE netblocks (nb CIDR UNIQUE NOT NULL, ...)
--
       nb      | ...
===============+======
192.168.1.0/24 | ...
192.168.0.0/16 | ...
10.0.0.0/8     | ...
0.0.0.0/0      | ...

如何有效地生成结果集:

       nb      | ips_logged
===============+============
192.168.1.0/24 | 2
192.168.0.0/16 | 1
10.0.0.0/8     | 1
4

3 回答 3

3

这在 8.3 上对我有用 - 在 8.4 上也应该没问题。我们需要一个自定义聚合,因为max(cidr)它不是内置的(即使>是)

create or replace function greatest_pair(cidr, cidr) 
                  returns cidr
                  language 'sql' immutable as 
$$select greatest($1, $2);$$;

create aggregate max( basetype = cidr, 
                      sfunc = greatest_pair, 
                      stype = cidr );

select max_nb, count(*)
from ( select ip, max(nb) as max_nb 
       from netblocks n join iplog i on(i.ip << n.nb)
       group by ip ) z
group by max_nb;

     max_nb     | count
----------------+-------
 192.168.1.0/24 |     2
 10.0.0.0/8     |     1
 192.168.0.0/16 |     1

如果您不想要自定义聚合,您可以执行以下操作:

create or replace view v as
select ip, nb from netblocks n join iplog i on(i.ip << n.nb);

select nb, count(*)
from ( select * 
       from v o 
       where not exists ( select * 
                          from v i 
                          where i.ip=o.ip and i.nb>o.nb ) ) z
group by nb;

或类似的使用with子句并且对 8.4 没有看法,但问题说得很有效率:-)

用这些视图测试:

create or replace view iplog as
select '192.168.1.100'::inet as ip union all
select '192.168.1.101'::inet union all
select '192.168.55.5'::inet union all
select '10.1.2.3'::inet;

create or replace view netblocks as
select '192.168.1.0/24'::cidr as nb union all
select '192.168.0.0/16'::cidr union all
select '10.0.0.0/8'::cidr union all
select '0.0.0.0/0'::cidr;
于 2010-12-07T16:04:50.780 回答
1

由于 IPv4 地址本质上是 4 个字节,因此它们可以表示为一个整数。您可以制作一个包含 netblock_start 和 netblock_end 的表(例如,192.168.1.0/24 将是 192.168.1.0 到 192.168.1.255,分别是 3232235776 到 3232235776),然后计数ip >= netblock_start && ip <= netblock_end(您的日志中的 IP 需要转换为相同的格式为此工作)。

于 2010-12-07T15:15:53.180 回答
0

@JackPDouglas 的回答非常出色。为了完整起见,这是我想出的天真的方法:

  SELECT nb, COUNT('X')
    FROM netblocks
    JOIN iplog
      ON ip << nb
         AND
         nb = (  SELECT nb
                   FROM netblocks
                  WHERE ip << nb
               ORDER BY nb DESC
                  LIMIT 1)
GROUP BY 1;

       nb       | count 
----------------+-------
 192.168.1.0/24 |     3
 192.168.0.0/16 |     1
 10.0.0.0/8     |     1
(3 rows)
于 2010-12-10T13:46:41.987 回答