Подсчет записей за наименьший сетевой блок (интервал) - PullRequest
1 голос
/ 07 декабря 2010

В SQL (postgresql 8.4.x), как я могу эффективно COUNT количество IP-записей, попадающих в наименьший сетевой блок из возможно охватывающих сетевых блоков?Например, я не хочу считать 10.0.0.1 как 10/8, так и 0/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

Ответы [ 3 ]

3 голосов
/ 07 декабря 2010

Это работает для меня на 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 голос
/ 07 декабря 2010

Поскольку 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 из вашего журнала необходимо преобразовать в тот же формат для этого, чтобы работать).

0 голосов
/ 10 декабря 2010

@ 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)
...