2013-04-23 18 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