【问题标题】:Counting records per smallest netblock (interval)每个最小网络块(间隔)的计数记录
【发布时间】:2010-12-07 15:07:28
【问题描述】:

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

更具体地说,给定:

-- 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

【问题讨论】:

  • 我认为您的问题和我的回答更适合dba.se - 如果您同意,您是否愿意考虑自我标记以进行迁移?我看到你已经有一个帐户...

标签: sql postgresql count intervals


【解决方案1】:

这在 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;

【讨论】:

  • +1 用于多种解决方案,聚合函数,并且通常以我没有想到的方式接近这个!
【解决方案2】:

由于 IPv4 地址本质上是 4 个字节,they can be represented as an integer。您可以制作一个包含 netblock_start 和 netblock_end 的表(例如,192.168.1.0/24 将是 192.168.1.0 到 192.168.1.255,分别是 3232235776 到 3232235776),然后计算 ip &gt;= netblock_start &amp;&amp; ip &lt;= netblock_end(需要将日志中的 IP 转换为使用相同的格式)。

【讨论】:

  • 这就是这样做的方法,但除非整数物理存储在表中,否则它不会有效。 Otoh,没有解决方案包括解析有效的IP。
  • 谢谢,Piskvor,但没必要。 CIDRINET 类型已经知道它们是间隔的:'1.2.3.4'::inet 最小包含间隔上进行分组有关。
  • @pilcrow:啊,我假设它在内部将它们转换为整数,很好。
  • 可能是。但是再次提出这个问题:您能否向我展示产生所需结果集的 SQL,而无需重复计算属于多个网络块的 IP?
【解决方案3】:

@JackPDouglas' answer 更胜一筹。为了完整起见,这是我想出的天真的方法:

  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)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多