英文:
Depth-first tree walk in parent-child table with sibling sorter for a total "sequencer"
问题
我使用的系统在父子表(相邻表)中存储分层数据:
代码 | 父代码 | 排序器 | 标题 |
---|---|---|---|
A | (NULL) | 0 | ... |
B | A | 0 | ... |
C | A | 1 | ... |
D | C | 0 | ... |
E | D | 0 | ... |
F | C | 1 | ... |
G | C | 2 | ... |
"排序器"列确定兄弟节点的顺序。代码 D、F、G 是兄弟节点(相同父节点的子节点),排序器可能会更改,例如,如果插入新行并要将其放在最后一个兄弟节点之前。例如,最后一个兄弟节点可以是类别,如“其他”,当然,无论稍后插入了更详细的类别,"其他"始终是最后一个兄弟节点。
简而言之:树可能会增长和变化。
对于下游系统,我需要一个树的“排序器” - 一个深度优先树遍历,从节点1(第一个根节点)到最后一个枚举行。这是否可以仅使用T-SQL(SQL Server 2016)来完成?
我之前尝试过一些使用CTE的实验,例如,我成功地创建了一个用于计算级别和完整路径的CTE。但是这些数据对于兄弟排序器没有影响;如果第一个和最后一个兄弟节点互换位置,级别和完整路径不会改变。排序器必须改变。
作为备用方案,我成功地在C#应用程序中使用Infragistics UltraTree绘制了树形结构,然后进行了递归迭代。这种方法有效,但不是SQL方法。SQL能否独自完成这项工作?
英文:
A system I use stores hierarchical data in a parent-child table (adjacency table):
Code | ParentCode | Sorter | Caption |
---|---|---|---|
A | (NULL) | 0 | ... |
B | A | 0 | ... |
C | A | 1 | ... |
D | C | 0 | ... |
E | D | 0 | ... |
F | C | 1 | ... |
G | C | 2 | ... |
The Sorter
column determines the sequence of siblings. Codes D, F, G are siblings (= children of same parent), and the sorter may change, e.g. if a new row is inserted and is to be placed before the last sibling. E.g., the last sibling could be a category like "other", and of course "other" is always the last sibling, even if more detailed categories are inserted later.
In short: the tree may grow and change.
For a downstream system, I'd need a "sequencer" of this tree - a depth-first tree-walk that enumerates the rows from node 1 (first root) to last. Can this be done in T-SQL (SQL Server 2016) alone?
I did some earlier experiments with CTE, I managed e.g. to make a CTE for calculating level and FullPath. But these data are unaffected on the sibling sorter; level and Fullpath do not change if first and last sibling changed places. The Sequencer would have to.
As a fall-back, I managed to draw the tree in an Infragistics UltraTree in a C# app, which I then iterated recursively. This works, but is a non-SQL-approach. Can SQL do this job on its own?
答案1
得分: 0
以下是翻译好的代码部分:
Solution 1: 使用CTE查询 - 将dt_code设置在WHERE子句中以从层次结构中提取根值...
WITH cte ( code, parent_code, level, path )
AS
(
-- 锚定成员 ...
SELECT dt_code, dt_parent_code, 1 AS level,
CAST( dt_sorter AS VARCHAR( MAX ) ) AS path
FROM data_table
WHERE dt_code = 'A'
UNION ALL
-- 递归成员 ...
SELECT dt_code, dt_parent_code, cte.level + 1 AS level,
CAST( cte.path + '.' + CAST( dt_sorter AS VARCHAR ) AS VARCHAR( MAX ) ) AS path
FROM data_table, cte
WHERE dt_parent_code = cte.code
)
SELECT code, parent_code, level, path
FROM cte
WHERE code = cte.code
ORDER BY path;
Solution 2: 使用ROW_NUMBER()和PARTITION - 也处理大于0-9的排序号码:
WITH cte ( code, parent_code, level, path )
AS
(
-- 锚定成员 ...
SELECT dt_code, dt_parent_code, 1 AS level,
CAST( RIGHT( '00000' + CONVERT( VARCHAR, ROW_NUMBER() OVER( PARTITION BY dt_parent_code ORDER BY dt_sorter ) ), 6 ) AS VARCHAR( MAX ) ) AS path
FROM data_table
WHERE dt_code = 'A'
UNION ALL
-- 递归成员 ...
SELECT dt_code, dt_parent_code, cte.level + 1 AS level,
CAST( cte.path + '.' + RIGHT( '00000' + CONVERT( VARCHAR, ROW_NUMBER() OVER( PARTITION BY dt_parent_code ORDER BY dt_sorter ) ), 6 ) AS VARCHAR( MAX ) ) AS path
FROM data_table, cte
WHERE dt_parent_code = cte.code
)
SELECT code, parent_code, level, path
FROM cte
WHERE code = cte.code
ORDER BY path;
英文:
This might help you get started:
CREATE TABLE data_table (
dt_code VARCHAR(2) NOT NULL,
dt_parent_code VARCHAR(2),
dt_sorter INT
);
INSERT data_table VALUES ( 'A', NULL, 0 );
INSERT data_table VALUES ( 'B', 'A', 0 );
INSERT data_table VALUES ( 'C', 'A', 1 );
INSERT data_table VALUES ( 'D', 'C', 0 );
INSERT data_table VALUES ( 'E', 'D', 0 );
INSERT data_table VALUES ( 'F', 'C', 1 );
INSERT data_table VALUES ( 'G', 'C', 2 );
Solution 1: Using a CTE to query - set the dt_code in the WHERE clause to the root value you want to extract from the hierarchy ...
WITH cte ( code, parent_code, level, path )
AS
(
-- Anchor member ...
SELECT dt_code, dt_parent_code, 1 AS level,
CAST( dt_sorter AS VARCHAR( MAX ) ) AS path
FROM data_table
WHERE dt_code = 'A'
UNION ALL
-- Recursive member ...
SELECT dt_code, dt_parent_code, cte.level + 1 AS level,
CAST( cte.path + '.' + CAST( dt_sorter AS VARCHAR ) AS VARCHAR( MAX ) ) AS path
FROM data_table, cte
WHERE dt_parent_code = cte.code
)
SELECT code, parent_code, level, path
FROM cte
WHERE code = cte.code
ORDER BY path;
Output:
code parent_code level path
---- ----------- ----- ----
A (null) 1 0
B A 2 0.0
C A 2 0.1
D C 3 0.1.0
E D 4 0.1.0.0
F C 3 0.1.1
G C 3 0.1.2
Solution 2: Using ROW_NUMBER() and PARTITION - also handle sorter numbers greater than 0-9:
WITH cte ( code, parent_code, level, path )
AS
(
-- Anchor member ...
SELECT dt_code, dt_parent_code, 1 AS level,
CAST( RIGHT( '00000' + CONVERT( VARCHAR, ROW_NUMBER() OVER( PARTITION BY dt_parent_code ORDER BY dt_sorter ) ), 6 ) AS VARCHAR( MAX ) ) AS path
FROM data_table
WHERE dt_code = 'A'
UNION ALL
-- Recursive member ...
SELECT dt_code, dt_parent_code, cte.level + 1 AS level,
CAST( cte.path + '.' + RIGHT( '00000' + CONVERT( VARCHAR, ROW_NUMBER() OVER( PARTITION BY dt_parent_code ORDER BY dt_sorter ) ), 6 ) AS VARCHAR( MAX ) ) AS path
FROM data_table, cte
WHERE dt_parent_code = cte.code
)
SELECT code, parent_code, level, path
FROM cte
WHERE code = cte.code
ORDER BY path;
Output:
code parent_code level path
---- ----------- ------ ----
A (null) 1 000001
B A 2 000001.000001
C A 2 000001.000002
D C 3 000001.000002.000001
E D 4 000001.000002.000001.000001
F C 3 000001.000002.000002
G C 3 000001.000002.000003
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论