Question

I have a table "test_networks" that is a list of networks with a description about what each network is and where it is located.

CREATE TABLE test_networks
(
  id serial PRIMARY KEY,
  address cidr,
  description text
);

The field "address" will be any of the following:

  • 10.0.0.0/8
  • 10.1.0.0/16
  • 10.1.1.0/24
  • 10.2.0.0/16
  • 10.3.0.0/16
  • 10.3.1.0/24
  • 10.3.2.0/24
  • 10.3.3.0/24
  • 10.15.1.0/24
  • 10.15.2.0/24
  • 10.15.3.0/24

I also have a table "test_systems" which contains a list of systems and their properties (I have a few more properties, but those are irrelevant):

CREATE TABLE test_systems
(
  id serial PRIMARY KEY,
  address inet,
  owner text
);

Lets assume I have systems with the following addresses:

  • 10.1.1.1
  • 10.2.0.1

I want to create a report of all systems and their closest networks description (or empty description if no network is found). As you can see, 10.1.1.1 matches multiple networks, so I only want to list the most specific one (i.e. the one with the highest masklen()) for each system. Example output would be:

  hostaddr |   netaddr   |  description
 ----------+-------------+----------------
  10.1.1.1 | 10.1.1.0/24 | third network
  10.2.0.1 | 10.2.0.0/16 | 4th network

I tried using this query:

SELECT s.address AS hostaddr, n.address AS netaddr, n.description AS description
FROM test_systems s
LEFT JOIN test_networks n
ON s.address << n.address;

However, this will give me a list of all system + network pairs, e.g.:

 hostaddr |   netaddr   |  description
----------+-------------+----------------
 10.1.1.1 | 10.0.0.0/8  | first network
 10.1.1.1 | 10.1.0.0/16 | second network
 10.1.1.1 | 10.1.1.0/24 | third network
 10.2.0.1 | 10.0.0.0/8  | first network
 10.2.0.1 | 10.2.0.0/16 | 4th network

Does anyone know how I can query for only the most specific network for each system?

Was it helpful?

Solution

You're looking for the "top n in group" query, where n = 1 in this case. You can do this using the row_number() window function:

SELECT x.hostaddr, x.netaddr, x.description FROM (
  SELECT
      s.address AS hostaddr,
      n.address AS netaddr,
      n.description AS description,
      row_number() OVER (
          PARTITION BY s.address
          ORDER BY masklen(n.address) DESC
      ) AS row
  FROM test_systems s
  LEFT JOIN test_networks n
  ON s.address << n.address
) x
WHERE x.row = 1;

OTHER TIPS

SELECT distinct on (s.address)
    s.address AS hostaddr,
    n.address AS netaddr,
    n.description AS description
FROM
    test_systems s
    LEFT JOIN
    test_networks n ON s.address << n.address
order by s.address, masklen(n.address) desc
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top