PostgreSQL:遞歸查詢應用場景

今天在罈子裏有人提出了一個問題,問題是這樣的:在如下指定表中sql

id name fatherid
1 中國 0
2 遼寧 1
3 山東 1
4 瀋陽 2
5 大連 2
6 濟南 3
7 和平區 4
8 瀋河區 4函數

如今給定一個id號,想獲得它完整的名字。如:
當id=7時,名字是:中國遼寧瀋陽和平區
當id=5時,名字是:中國遼寧大連性能

id是任意給定的,不肯定在哪一層。遞歸往上找,直到 fatherid=0 爲止。也就是最高層級時結束,
求完整SQL語句。測試

看到這個問題,第一想到的是能夠用 PG的遞歸查詢實現,以前也寫過相似的例子,
http://francs3.blog.163.com/b...,但以前的例子
是向下遞歸,而這裏的需求是向上遞歸,略有不一樣,因而忍不住演示下:code

這個問題的思路是分兩步走,第一步:查詢出指定節點的父節點;第二步:將查詢出的全部父節點排列到一行。blog

--1 建立測試表,並插入測試數據遞歸

skytf=> create table test_area(id int4,name varchar(32),fatherid int4);
CREATE TABLE

insert into test_area values (1, '中國'   ,0);
insert into test_area values (2, '遼寧'   ,1);
insert into test_area values (3, '山東'   ,1);
insert into test_area values (4, '瀋陽'   ,2);
insert into test_area values (5, '大連'   ,2);
insert into test_area values (6, '濟南'   ,3);
insert into test_area values (7, '和平區' ,4);
insert into test_area values (8, '瀋河區' ,4);
skytf=> select * From test_area;
 id |  name  | fatherid 
----+--------+----------
  1 | 中國   |        0
  2 | 遼寧   |        1
  3 | 山東   |        1
  4 | 瀋陽   |        2
  5 | 大連   |        2
  6 | 濟南   |        3
  7 | 和平區 |        4
  8 | 瀋河區 |        4
(8 rows)

--2 查詢指定節點如下的全部節點索引

WITH RECURSIVE r AS ( 

       SELECT * FROM test_area WHERE id = 4 
     union   ALL 
       SELECT test_area.* FROM test_area, r WHERE test_area.fatherid = r.id 
     ) 
SELECT * FROM r ORDER BY id;
 id |  name  | fatherid 
----+--------+----------
  4 | 瀋陽   |        2
  7 | 和平區 |        4
  8 | 瀋河區 |        4
(3 rows)

備註:一般的用法是查詢指定節點以及指定節點如下的全部節點,那麼本貼的需求恰好相反,須要查詢指定節點以上的全部節點。ci

--3 查詢指定節點以上的全部節點get

WITH RECURSIVE r AS ( 
       SELECT * FROM test_area WHERE id = 4 
     union   ALL 
       SELECT test_area.* FROM test_area, r WHERE test_area.id = r.fatherid 
     ) 
 SELECT * FROM r ORDER BY id;

 id | name | fatherid 
----+------+----------
  1 | 中國 |        0
  2 | 遼寧 |        1
  4 | 瀋陽 |        2
(3 rows)

備註:這正是咱們想要的結果,接下來須要將 name 字段結果集合併成一行,我這裏想到的是建立個 function,固然也有其它方法。

--4 create funcion

CREATE or replace FUNCTION func_get_area(in in_id int4, out o_area text)  AS 

$$ 
DECLARE
   v_rec_record RECORD;
BEGIN

  o_area = '';
  FOR v_rec_record IN (WITH RECURSIVE r AS (SELECT *
                           FROM test_area
                          WHERE id = in_id
                         union ALL
                         SELECT test_area.*
                           FROM test_area, r
                          WHERE test_area.id = r.fatherid)SELECT name
                         FROM r
                        ORDER BY id) LOOP
    o_area := o_area || v_rec_record.name;
  END LOOP;
  return;
END; 
$$
LANGUAGE 'plpgsql';

備註:函數的做用爲拼接 name 字段。

--5 測試

skytf=> select func_get_area(7) ;
   func_get_area    
--------------------
 中國遼寧瀋陽和平區
(1 row)

skytf=> select func_get_area(5) ;
 func_get_area 
---------------
 中國遼寧大連
(1 row)

備註:正好實現了需求,當表數據量較大時,考慮到性能,建議在表 test_area 字段 id,fatherid 上創建單獨的索引。

相關文章
相關標籤/搜索