检索按PostgreSQL的Ltree模块下的列排序的完整层次结构

Dav*_*ard 9 database postgresql tree hierarchy

我正在使用PostgreSQL的Ltree模块来存储分层数据.我希望检索按特定列排序的完整层次结构.

请考虑下表:

  votes | path  | ...
 -------+-------+-----
      1 | 1     | ...
      2 | 1.1   | ...
      4 | 1.2   | ...
      1 | 1.2.1 | ...
      3 | 2     | ...
      1 | 2.1   | ...
      2 | 2.1.1 | ...
      4 | 2.1.2 | ...
    ... | ...   | ...
Run Code Online (Sandbox Code Playgroud)

在我当前的实现中,我将查询数据库SELECT * FROM comments ORDER BY path,它将返回整个树:

Node 1
-- Node 1.1
-- Node 1.2
---- Node 1.2.1
Node 2
-- Node 2.1
---- Node 2.1.1
---- Node 2.1.2
Run Code Online (Sandbox Code Playgroud)

但是,我想排序votes(而不是id,按path数量排序).每个深度级别都需要独立排序,正确的树形结构保持不变.会返回以下内容的东西:

Node 2
-- Node 2.1
---- Node 2.1.2
---- Node 2.1.1
Node 1
-- Node 1.2
---- Node 1.2.1
-- Node 1.1
Run Code Online (Sandbox Code Playgroud)

Postgres' WITH RECURSIVE可能合适,但我不确定.有任何想法吗?

Erw*_*ter 12

你走在正确的轨道上WITH RECURSIVE.

使用递归CTE的解决方案

WITH RECURSIVE t AS (
    SELECT t.votes
         , t.path
         , 1::int AS lvl
         , to_char(t2.votes, 'FM0000000')  AS sort
    FROM   tbl t
    JOIN   tbl t2 ON t2.path = subltree(t.path, 0, 1)

    UNION ALL
    SELECT t.votes
         , t.path
         , t.lvl + 1
         , t.sort || to_char(t2.votes, 'FM0000000')
    FROM   t
    JOIN   tbl t2 ON t2.path = subltree(t.path, 0, t.lvl + 1)
    WHERE  nlevel(t.path) > t.lvl
    )
SELECT votes, path, max(sort) AS sort
FROM   t
GROUP  BY 1, 2
ORDER  BY max(sort), path;
Run Code Online (Sandbox Code Playgroud)

主要观点

  • 关键部分是用值替换路径的每个级别votes.因此,我们最后组装了一列ORDER BY.这是必要的,因为路径具有未知深度,我们无法通过静态SQL中的未知数量的表达式进行排序.

  • 为了获得稳定的排序,我转换votes为使用前导零的字符串to_char().我在演示中使用七位数,适用于低于10.000.000的投票值.根据您的最高投票数量进行调整.

  • 在最后SELECT我排除所有中间状态以消除重复.只剩下最后一步了max(sort).

  • 这适用于带有递归CTE的标准SQL,但对于大型树来说效率不高.plpgsql函数以递归方式更新临时表中的排序路径而不创建临时欺骗可能会表现得更好.

  • 仅适用于安装的ltree模块.函数subltree(...)和nlevel(.)以及ltree日期类型不是标准PostgreSQL的一部分.


我的测试设置,方便您查看:

CREATE TEMP TABLE tbl(votes int, path ltree);
INSERT INTO tbl VALUES
  (1, '1')
, (2, '1.1')
, (4, '1.2')
, (1, '1.2.1')
, (3, '2')
, (1, '2.1')
, (2, '2.1.1')
, (4, '2.1.2')
, (1, '2.1.3')
, (2, '3')
, (17, '3.3')
, (99, '3.2')
, (10, '3.1.1')
, (2345, '3.1.2')
, (1, '3.1.3');
Run Code Online (Sandbox Code Playgroud)

PL/pgSQL表函数做同样的事情

大树应该更快.

CREATE OR REPLACE FUNCTION f_sorted_ltree()
  RETURNS TABLE(votes int, path ltree) AS
$func$
DECLARE
   lvl integer := 0;
BEGIN
   CREATE TEMP TABLE t ON COMMIT DROP AS
   SELECT tbl.votes
        , tbl.path
        , ''::text AS sort
        , nlevel(tbl.path) AS depth
   FROM   tbl;

   -- CREATE INDEX t_path_idx ON t (path);   -- beneficial for huge trees
   -- CREATE INDEX t_path_idx ON t (depth);

   LOOP
      lvl := lvl + 1;

      UPDATE t SET sort = t.sort || to_char(v.votes, 'FM0000000')
      FROM  (
         SELECT t2.votes, t2.path
         FROM   t t2
         WHERE  t2.depth = lvl
         ) v
      WHERE  v.path = subltree(t.path, 0 ,lvl);

      EXIT WHEN NOT FOUND;
   END LOOP;

   -- Return sorted rows
   RETURN QUERY
   SELECT t.votes, t.path
   FROM   t
   ORDER  BY t.sort;
END
$func$  LANGUAGE plpgsql VOLATILE;
Run Code Online (Sandbox Code Playgroud)

呼叫:

SELECT * FROM f_sorted_ltree();
Run Code Online (Sandbox Code Playgroud)

请阅读有关设置temp_buffers手册.

我很感兴趣,它可以更快地显示你的真人生活数据.