让我们说我有一个使用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
我想选择节点,以便:
像这样:
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
可以满足第一个要求,但我不知道如何实现第二个要求,这是否可能?
答案 0 :(得分:2)
I am solving this with a second ltree sort_path
and some triggers.
Ultimately you will sort on the sort_path tree whose values are based on an lpad of the sort column of all the ancestors plus the lpad of the sort column of the current row.
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
BTW even the simple path sort you have is not quite right, you will have trouble as soon as you hit double digit path segments since the path sort is alpha-based, not numeric.
Note that a complete solution includes a trigger that recalculates the sort_path value for all descendants when the sort value is changed for any parent node row.
Example implementation:
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();