sql - 使用 PostgreSQL ltree 对节点进行排序

标签 sql postgresql tree

假设我使用 ltree 存储了一棵树:

   id   |   path   |   sort   
------------------------------
0       |0         |1
1       |0.1       |2
2       |0.1.2     |3
3       |0.1.3     |1
4       |0.1.4     |2
5       |0.5       |3
6       |0.6       |1

我想选择节点以便:

  1. 子节点出现在父节点之后;
  2. 具有较小“排序”值的兄弟节点首先出现;

像这样:

   id   |   path   |   sort   
------------------------------
0       |0         |1
6       |0.6       |1
1       |0.1       |2
3       |0.1.3     |1
4       |0.1.4     |2
2       |0.1.2     |3
5       |0.5       |3

第一个要求可以通过 ORDER BY path 实现,但我不知道如何实现第二个,这是否可能?

最佳答案

我正在用第二个 ltree sort_path 和一些触发器来解决这个问题。

最终,您将在 sort_path 树上排序,其值基于所有祖先的排序列的 lpad 加上当前行的排序列的 lpad。

   id   |   path   |   rank   |  sort_path
--------------------------------------------
0       |0         |1         | 0001
6       |0.6       |1         | 0001.0001
1       |0.1       |2         | 0001.0002
3       |0.1.3     |1         | 0001.0002.0001
4       |0.1.4     |2         | 0001.0002.0002
2       |0.1.2     |3         | 0001.0002.0003
5       |0.5       |3         | 0001.0003

顺便说一句,即使您拥有的简单路径排序也不是很正确,一旦您遇到两位数的路径段,您就会遇到麻烦,因为路径排序是基于字母的,而不是数字。

请注意,完整的解决方案包括一个触发器,当任何父节点行的排序值发生更改时,它会重新计算所有后代的 sort_path 值。

示例实现:

CREATE EXTENSION IF NOT EXISTS ltree;

CREATE TABLE tree_nodes (path LTREE, rank INT, sort_path LTREE);

CREATE OR REPLACE FUNCTION calc_sort_path(tree_path LTREE, sibling_rank INT) RETURNS LTREE AS $$
DECLARE
  sort_ranks TEXT[];
  sort_path LTREE;
  ancestor RECORD;
BEGIN
  -- Default to the segment text (prepended with underscore).
  -- If some ancestors are missing, this ensures the children will still sort together.
  FOR iterator IN 1..NLEVEL(tree_path) LOOP
    sort_ranks[iterator] := '_' || SUBPATH(tree_path, iterator-1, 1)::TEXT;
  END LOOP;
  -- Format a sort rank path segment for each ancestor.
  FOR ancestor IN
    SELECT NLEVEL(tree_nodes.path) AS level, tree_nodes.rank FROM tree_nodes
      WHERE tree_nodes.path @> tree_path AND tree_nodes.path != tree_path
  LOOP
    sort_ranks[ancestor.level] := LPAD(ancestor.rank::TEXT, 4, '0');
  END LOOP;
  -- Format a final sort rank path segment for this leaf node.
  sort_ranks[NLEVEL(tree_path)] := LPAD(sibling_rank::TEXT, 4, '0');
  -- Convert array to LTREE path.
  SELECT STRING_AGG(padded_rank, '.')::LTREE INTO sort_path FROM
    (SELECT UNNEST(sort_ranks) AS padded_rank) path_ranks;

  RETURN sort_path;
END
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION update_sort_paths() RETURNS trigger AS $$
DECLARE
  has_changed BOOLEAN;
BEGIN
  has_changed := TG_OP = 'UPDATE' AND (OLD.path IS DISTINCT FROM NEW.path OR OLD.rank IS DISTINCT FROM NEW.rank);
  IF (TG_OP = 'DELETE' OR has_changed) THEN
    UPDATE tree_nodes SET sort_path = calc_sort_path(path, rank) WHERE OLD.path @> path;
  END IF;
  IF (TG_OP = 'INSERT' OR has_changed) THEN
    UPDATE tree_nodes SET sort_path = calc_sort_path(path, rank) WHERE NEW.path @> path;
  END IF;
  RETURN NULL;
END;
$$ LANGUAGE plpgsql;

DROP TRIGGER IF EXISTS on_rank_change ON tree_nodes;
CREATE TRIGGER on_rank_change AFTER INSERT OR UPDATE OR DELETE ON tree_nodes
    FOR EACH ROW EXECUTE PROCEDURE update_sort_paths();

关于sql - 使用 PostgreSQL ltree 对节点进行排序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49850810/

相关文章:

java - 圆图的数据结构与算法

c - 如何递归/迭代释放我的数据结构的所有节点?

java - 在 Eclipse 中,如何获取 Java 字符串中的 SQL 文本?

sql - 连续分组依据

java - HQL、PostgreSQL : Not in clause not working, 语法错误)

postgresql - 将 PostgreSQL 通知放入 AWS SQS 队列

sql - 减少 SQL 中的行数

d3.js - 避免 d3.js 中树形布局中的节点重叠

sql - 如何将变量解析为 dbt 中的源引用?

mysql - 使用 % 和 IN 子句进行查询