2010-12-07 63 views
1

Dans SQL (postgresql 8.4.x), comment puis-je efficacement COUNT le nombre d'enregistrements IP entrant dans le plus petit netblock d'englober éventuellement netblocks? Je ne veux pas compter 10.0.0.1 sous les deux 10/8 et 0/0, par exemple.records de comptage par le plus petit netblock (intervalle)

Plus concrètement, étant donné:

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

Comment puis-je produire efficacement le jeu de résultats:

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

Je pense que votre question et ma réponse serait un meilleur ajustement sur le [dba.se] (http: // dba .stackexchange.com /) - Si vous êtes d'accord, êtes-vous prêt à considérer l'auto-signalisation pour la migration? Je vois que vous avez déjà un compte ... –

Répondre

3

Cela fonctionne pour moi sur 8.3 - cela devrait bien se passer sur 8.4 aussi. Nous avons besoin d'un agrégat personnalisé parce max(cidr) n'est pas intégré (même si > est)

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 

Si vous ne voulez pas l'agrégat personnalisé, vous pouvez le faire:

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; 

ou similaire en utilisant un with clause et pas de vue sur 8.4, mais la question a dit efficacement :-)

testé avec ces vues:

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; 
+0

+1 pour des solutions multiples, la fonction d'agrégat, et généralement approcher cela d'une manière à laquelle je n'avais pas pensé! – pilcrow

1

Depuis les adresses IPv4 sont essentiellement 4 octets, they can be represented as an integer. Vous pouvez créer une table contenant netblock_start et netblock_end (par exemple 192.168.1.0/24 serait 192.168.1.0 à 192.168.1.255, respectivement 3232235776 à 3232235776), puis compter ip >= netblock_start && ip <= netblock_end (l'adresse IP de votre journal doit être convertie en la même format pour que cela fonctionne).

+0

C'est la façon de le faire, mais à moins que les entiers ne soient stockés physiquement dans les tables, cela ne sera pas efficace. Otoh, il n'y a pas de solution, y compris l'analyse des adresses IP qui seront effecient. –

+0

Merci, Piskvor, mais pas nécessaire. Les types 'CIDR' et' INET' comprennent déjà qu'ils sont intervalles: '1.2.3.4' :: inet << '0.0.0.0/0'::cidr. La question a à voir avec le regroupement seulement sur le * plus petit * intervalle englobant. – pilcrow

+0

@pilcrow: Ah, je suppose que ça les convertit en entiers en interne, c'est bien. – Piskvor

0

@JackPDouglas' answer est supérieur. Pour être complet, c'est l'approche naïve, je suis venu avec le dessus de ma tête:

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)