2013-04-23 73 views
2

我有一个表“test_networks”,它是一个网络列表,其中包含有关每个网络的位置以及位置的说明。Postgresql只加入最具体的cidr匹配

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

字段 “地址” 将是以下任意的:

  • 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

我也有一个表“test_systems”,其中包含的系统的列表和它们的属性(我有几个特性,但这些都无关紧要):

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

让我们假设我有系统s的以下地址:

  • 10.1.1.1
  • 10.2.0.1

我要创造,如果没有网络的所有系统的报告和自己最亲近的网络描述(或空描述被找到)。正如你所看到的,10.1.1.1匹配多个网络,所以我只想列出每个系统中最特定的一个(即具有最高的masklen())。例如输出为:

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 

我尝试使用此查询:

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; 

然而,这会给我所有的系统+网络对的列表,如:

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 

不任何人都知道我如何才能查询每个系统的最具体网络?

+0

仅供参考,'PRIMARY KEY'意味着'不NULL'。您不需要为主键指定NOT NULL。 – cdhowie 2013-04-23 14:36:20

回答

5

您正在查找“top n in group”查询,其中n = 1。您可以使用row_number()窗口功能做到这一点:

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

这确实_exactly_我​​所需要的。我花了一整天的时间尝试自行解决这个问题,但都失败了。小心解释这里发生了什么? – agnsaft 2013-04-23 14:49:34

+0

@invictus如果将'x.row'添加到由外部查询选择的列中,则应该更明显一些。窗口函数'row_number()'为从1开始的结果集中的所有行编号。'PARTITION BY s。address'意思是's.address'的每个不同值都有自己的一组行号,从1开始,'ORDER BY'子句意味着具有最大'masklen(n.address)'的那个将接收值为1.所以从那里我们只需要筛选没有'row_number()'为1的行,这就是'SELECT'外部的行。 – cdhowie 2013-04-23 14:53:03

+0

很酷。是否可以使用任何特定的索引来加快速度? – agnsaft 2013-04-23 15:00:36

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

感谢您的输入。这个解决方案比上面提供的解决方案有什么优点/缺点? – agnsaft 2013-04-23 15:44:11

+0

@invictus我认为它更简单。在两者上运行'explain analize'并检查哪一个是最快的。 – 2013-04-23 18:31:31