假设有一个表存储这样的层次结构:
item_id | hierarchical_id
--------+-----------------
1 | ;1;
2 | ;1;2;
3 | ;1;2;3;
4 | ;1;2;4;
5 | ;1;2;4;5;
此处存储的层次结构为1,为root,2为1的子项,3为4的子项,5为4的子项。
查询
SELECT
-- the substr is used to remove the first and last semicolumns
regexp_split_to_table(substr(hierarchical_id, 2, length(hierarchical_id) - 2)
, E';'
) as parent_id,
item_id,
hierarchical_id
FROM
table
返回
parent_id | item_id | hierarchical_id
----------+---------+-----------------
1 | 1 | ;1;
1 | 2 | ;1;2;
2 | 2 | ;1;2;
1 | 3 | ;1;2;3;
3 | 3 | ;1;2;3;
1 | 4 | ;1;2;3;
2 | 4 | ;1;2;4;
4 | 4 | ;1;2;4;
1 | 5 | ;1;2;4;5;
2 | 5 | ;1;2;4;5;
4 | 5 | ;1;2;4;5;
5 | 5 | ;1;2;4;5;
如何修改查询以获得第四列:
parent_id | item_id | hierarchical_id | distance
----------+---------+-----------------+---------
1 | 1 | ;1; | 0
1 | 2 | ;1;2; | 1
2 | 2 | ;1;2; | 0
1 | 3 | ;1;2;3; | 2
2 | 3 | ;1;2;3; | 1
3 | 3 | ;1;2;3; | 0
1 | 4 | ;1;2;4; | 2
2 | 4 | ;1;2;4; | 1
4 | 4 | ;1;2;4; | 0
1 | 5 | ;1;2;4;5; | 3
2 | 5 | ;1;2;4;5; | 2
4 | 5 | ;1;2;4;5; | 1
5 | 5 | ;1;2;4;5; | 0
distance
的含义是当前行中item_id
和parent_id
之间的距离。例如:节点与其自身之间的距离为0,节点与其父节点之间的距离为1,节点与其父节点之间的距离为2等。它不必从0开始。
row_number
可以正常工作,如果我可以让每个等级item_id
的组重新开始,因为hierarchical_id
中的ID是有序的。
有什么建议吗?
答案 0 :(得分:4)
窗口功能为您提供了很多控制;见4.2.8. Window Function Calls。
您需要的关键是:
row_number() OVER (PARTITON BY item_id ORDER BY hierarchical_id)
鉴于数据:
create table t ( item_id integer, hierarchical_id text );
insert into t (item_id, hierarchical_id) values
(1,';1;'),
(2,';1;2;'),
(3,';1;2;3;'),
(4,';1;2;4;'),
(5,';1;2;4;5;');
查询:
WITH x AS (
SELECT regexp_split_to_table(substr(hierarchical_id, 2, length(hierarchical_id) - 2), E';') as parent_id,
item_id,
hierarchical_id
FROM t
)
SELECT
*,
row_number() OVER (PARTITION BY item_id ORDER BY parent_id DESC) - 1 AS distance
FROM x
ORDER BY item_id, parent_id;
产生
parent_id | item_id | hierarchical_id | distance
-----------+---------+-----------------+----------
1 | 1 | ;1; | 0
1 | 2 | ;1;2; | 1
2 | 2 | ;1;2; | 0
1 | 3 | ;1;2;3; | 2
2 | 3 | ;1;2;3; | 1
3 | 3 | ;1;2;3; | 0
1 | 4 | ;1;2;4; | 2
2 | 4 | ;1;2;4; | 1
4 | 4 | ;1;2;4; | 0
1 | 5 | ;1;2;4;5; | 3
2 | 5 | ;1;2;4;5; | 2
4 | 5 | ;1;2;4;5; | 1
5 | 5 | ;1;2;4;5; | 0
看起来大致正确,但由于您的预期输出似乎与我运行时提供的查询输出(第9.1页)不匹配,因此很难确定。
答案 1 :(得分:1)
现在这个问题得到了更好的改进,这是一个更好地表达意图的表述,而不仅仅是结果:
CREATE EXTENSION intarray;
SELECT
exploded.*,
array_length(h_arr,1) - idx(h_arr,parent_id) AS distance
FROM (
SELECT unnest(h_arr) AS parent_id, item_id, h_arr
FROM (
SELECT
item_id,
regexp_split_to_array( trim(hierarchical_id,';'),';')::int[] as h_arr
FROM t
) h_as_intarray
) exploded;
......虽然由于所需的所有传球而有点慢。如果hierarchical_id
首先存储为整数数组,可能应该是:
ALTER TABLE t ALTER COLUMN hierarchical_id TYPE int[]
USING (regexp_split_to_array( trim(hierarchical_id,';'),';')::int[]);
你有一个更好的查询,因为你将摆脱所有可怕的正则表达式字符串处理:
SELECT
exploded.*,
array_length(hierarchical_id,1) - idx(hierarchical_id,parent_id) AS distance
FROM (
SELECT unnest(hierarchical_id) AS parent_id, item_id, hierarchical_id
FROM t
) exploded;
...对于这个微小的数据集,奖励积分也快3倍,并且很可能在更大的数据集上保持或扩大领先优势。