我有一个如下的父子表:
child | father
H : G
F : G
G : D
E : D
A : E
B : C
C : E
我希望sql server能够生成类似这样的内容(正如在{问题Convert a series of parent-child relationships into a hierarchical tree?中提到的那样,但是在tsql而不是在php中):
D
├── E
│ ├── C
│ │ └── B
│ └── A
└── G
├── F
└── H
当然结果可以是我可以在文本编辑器中复制的字符串列。
我还希望有第二个查询生成如下内容:
father | descendants
D | D -> E -> C -> B
D | D -> E -> A
D | D -> G -> F
D | D -> G -> H
在前一种情况下,只有一棵树有一个父亲,但在表格中可能会有更多的父亲有多个父亲,如果D不存在,这棵树也会如此。
如果requast的第一部分(伪视觉树)不可能做到这一点很好。重要的是表格。
我已经尝试做了类似的事情,因为我无法获得想要的结果。
TNX
答案 0 :(得分:3)
这很有趣。但是,在SQL而不是其他语言中执行此操作可能效率低下。想想还是很有趣。
这是我的表现。
初始化表格:
SET NOCOUNT ON
DECLARE @Table TABLE ([Child] NVARCHAR(10), [Parent] NVARCHAR(10))
INSERT @Table VALUES ('H','G'),('F','G'),('G','D'),('E','D')
,('A','E'),('B','C'),('C','E'),('D', NULL),('Z','E'),('X','Z'),('Y','Z')
,('L',NULL),('M','L'),('N','L'),('P','N'),('Q','L'), ('R',NULL),('S', 'R')
IF OBJECT_ID('tempdb..#tmptable') IS NOT NULL DROP TABLE #tmptable
; WITH T AS (
SELECT Parent, Child, 1 [Level]
FROM @Table
WHERE Parent IS NULL
UNION ALL
SELECT a.Parent, a.Child, T.[Level] + 1
FROM @Table a
JOIN T ON a.Parent = T.Child)
SELECT *
INTO #tmptable
FROM T
对于查询1,我假设您不知道任何父母可能拥有的最大后代数量,我使用动态SQL:
DECLARE @SQL NVARCHAR(MAX)
DECLARE @a INT = (SELECT MAX(Level) FROM #tmptable)
DECLARE @b INT = 2
SET @SQL =
'; WITH CTE AS (
SELECT T1.Child Father'
WHILE @b<= @a BEGIN
SET @SQL += '
, ISNULL(T' + CONVERT(NVARCHAR, @b) + '.Child, '''') Child' + CONVERT(NVARCHAR, @b - 1)
SET @b += 1
END
SET @SQL +='
, ROW_NUMBER() OVER (ORDER BY T1.Child'
SET @b = 2
WHILE @b <= @a BEGIN
SET @SQL += ', T' + CONVERT(NVARCHAR, @b) + '.Child'
SET @b += 1
END
SET @SQL += ') RN
FROM #tmptable T1'
SET @b = 2
WHILE @b <= @a BEGIN
SET @SQL += '
LEFT JOIN #tmptable T' + CONVERT(NVARCHAR, @b) + ' ON T' + CONVERT(NVARCHAR, @b) +'.Parent = T' + CONVERT(NVARCHAR, @b - 1) + '.Child'
SET @b += 1
END
SET @SQL += '
WHERE T1.Parent IS NULL
GROUP BY T1.Child'
SET @b = 2
WHILE @b <= @a BEGIN
SET @SQL += ', T' + CONVERT(NVARCHAR, @b) + '.Child'
SET @b += 1
END
SET @SQL += ')
SELECT ''<ul>'' + REPLACE(REPLACE(CONVERT(NVARCHAR(MAX), (
SELECT CASE WHEN RN = 1 THEN ''<li>''
WHEN (SELECT Father FROM CTE WHERE RN = C.RN - 1) <> Father THEN ''<li>''
ELSE '''' END --Fatherli
, CASE WHEN RN = 1 THEN Father
WHEN (SELECT Father FROM CTE WHERE RN = C.RN - 1) <> Father THEN Father
ELSE '''' END --Father
, CASE WHEN RN = 1 THEN ''</li>''
WHEN (SELECT Father FROM CTE WHERE RN = C.RN - 1) <> Father THEN ''</li>''
ELSE '''' END --Fathercli
, CASE WHEN RN = 1 AND Child1 <> '''' THEN ''<ul>''
WHEN (SELECT Father FROM CTE WHERE RN = C.RN - 1) <> Father AND Child1 <> '''' THEN ''<ul>''
ELSE '''' END --Fatherul'
SET @b = 2
WHILE @b <= @a BEGIN
SET @SQL += '
, CASE WHEN RN = 1 AND Child' + CONVERT(NVARCHAR, @b-1) + ' <> '''' THEN ''<li>''
WHEN (SELECT Child' + CONVERT(NVARCHAR, @b-1) + ' FROM CTE WHERE RN = C.RN - 1) <> Child' + CONVERT(NVARCHAR, @b-1) + ' AND Child' + CONVERT(NVARCHAR, @b-1) + ' <> '''' THEN ''<li>''
ELSE '''' END --Child' + CONVERT(NVARCHAR, @b-1) + 'li
, CASE WHEN RN = 1 AND Child' + CONVERT(NVARCHAR, @b-1) + ' <> '''' THEN Child' + CONVERT(NVARCHAR, @b-1) + '
WHEN (SELECT Child' + CONVERT(NVARCHAR, @b-1) + ' FROM CTE WHERE RN = C.RN - 1) <> Child' + CONVERT(NVARCHAR, @b-1) + ' AND Child' + CONVERT(NVARCHAR, @b-1) + ' <> '''' THEN Child' + CONVERT(NVARCHAR, @b-1) + '
ELSE '''' END --Child' + CONVERT(NVARCHAR, @b-1) + '
, CASE WHEN RN = 1 AND Child' + CONVERT(NVARCHAR, @b-1) + ' <> '''' THEN ''</li>''
WHEN (SELECT Child' + CONVERT(NVARCHAR, @b-1) + ' FROM CTE WHERE RN = C.RN - 1) <> Child' + CONVERT(NVARCHAR, @b-1) + ' AND Child' + CONVERT(NVARCHAR, @b-1) + ' <> '''' THEN ''</li>''
ELSE '''' END --Child' + CONVERT(NVARCHAR, @b-1) + 'cli'
IF @a <> @b
SET @SQL += '
, CASE WHEN RN = 1 AND Child' + CONVERT(NVARCHAR, @b-1) + ' <> '''' AND Child' + CONVERT(NVARCHAR, @b) + ' <> '''' THEN ''<ul>''
WHEN (SELECT Child' + CONVERT(NVARCHAR, @b-1) + ' FROM CTE WHERE RN = C.RN - 1) <> Child' + CONVERT(NVARCHAR, @b-1) + ' AND Child' + CONVERT(NVARCHAR, @b) + ' <> '''' THEN ''<ul>''
ELSE '''' END --Child' + CONVERT(NVARCHAR, @b-1) + 'ul'
SET @b += 1
END
SET @b -= 3
WHILE @b > 0 BEGIN
SET @SQL += '
, CASE WHEN RN = (SELECT MAX(RN) FROM CTE) AND Child' + CONVERT(NVARCHAR, @b+1) + ' <> '''' THEN ''</ul>''
WHEN (SELECT Child' + CONVERT(NVARCHAR, @b) + ' FROM CTE WHERE RN = C.RN + 1) <> Child' + CONVERT(NVARCHAR, @b) + ' AND Child' + CONVERT(NVARCHAR, @b+1) + ' <> '''' THEN ''</ul>''
ELSE '''' END --Child' + CONVERT(NVARCHAR, @b) + 'cul'
SET @b -= 1
END
SET @SQL += '
, CASE WHEN RN = (SELECT MAX(RN) FROM CTE) AND Child1 <> '''' THEN ''</ul>''
WHEN (SELECT Father FROM CTE WHERE RN = C.RN + 1) <> Father AND Child1 <> '''' THEN ''</ul>''
ELSE '''' END --Fathercul
FROM CTE C
FOR XML PATH (''''))), ''<'', ''<''), ''>'', ''>'') + ''</ul>'''
EXEC(@SQL)
-- PRINT @SQL
输出(对于我输入的值)是<ul><li>D</li><ul><li>E</li><ul><li>A</li><li>C</li><ul><li>B</li></ul><li>Z</li><ul><li>X</li><li>Y</li></ul></ul><li>G</li><ul><li>F</li><li>H</li></ul></ul><li>L</li><ul><li>M</li><li>N</li><ul><li>P</li></ul><li>Q</li></ul><li>R</li><ul><li>S</li></ul></ul>
,显示如下:
对于第二个查询,可能有更简单的方法,但我想为什么不使用更动态的SQL?
DECLARE @i INT = (SELECT MAX([Level]) FROM #tmptable), @j INT = 2
DECLARE @SQL2 NVARCHAR(MAX)
SET @SQL2 = 'SELECT T1.Child Father, T1.Child '
WHILE @j <= @i BEGIN
SET @SQL2 += '+ ISNULL('' -> '' + T' + CONVERT(NVARCHAR, @j) + '.Child, '''')'
SET @j += 1
END
SET @j = 2
SET @SQL2 += ' Descendants FROM #tmptable T1'
WHILE @j <= @i BEGIN
SET @SQL2 += ' LEFT JOIN #tmptable T' + CONVERT(NVARCHAR, @j) + ' ON T' + CONVERT(NVARCHAR, @j) + '.[Parent] = T' + CONVERT(NVARCHAR, @j-1) + '.[Child]'
SET @j += 1
END
SET @j = 2
SET @SQL2 += ' WHERE T1.[Parent] IS NULL ORDER BY T1.[Child]'
WHILE @j <= @i BEGIN
SET @SQL2 += ', T' + CONVERT(NVARCHAR, @j) + '.[Child]'
SET @j += 1
END
EXEC(@SQL2)
答案 1 :(得分:2)
为了避免扫描整个表格以获得最大的祖先,我会添加father
= NULL
行或任何可以指示它的行。
要获取从祖先到叶子的路径,您可以使用CTE:
CREATE TABLE #tab(
child VARCHAR(8) NOT NULL PRIMARY KEY
,father VARCHAR(4) NULL
);
INSERT INTO #tab(child,father) VALUES ('H','G');
INSERT INTO #tab(child,father) VALUES ('F','G');
INSERT INTO #tab(child,father) VALUES ('G','D');
INSERT INTO #tab(child,father) VALUES ('E','D');
INSERT INTO #tab(child,father) VALUES ('A','E');
INSERT INTO #tab(child,father) VALUES ('B','C');
INSERT INTO #tab(child,father) VALUES ('C','E');
INSERT INTO #tab(child,father) VALUES ('D',NULL); -- add by me
INSERT INTO #tab(child,father) VALUES ('Z',NULL); -- for testing
INSERT INTO #tab(child,father) VALUES ('Z1','Z');
INSERT INTO #tab(child,father) VALUES ('Z2','Z');
查询:
;WITH cte AS
(
SELECT
Child
,Father
,Level = 1
,Path = CAST(Child AS NVARCHAR(MAX))
,Ancestor = Child
FROM #tab
WHERE father IS NULL
UNION ALL
SELECT
t.Child
,t.Father
,Level = Level + 1
,Path = c.Path + ' -> ' + t.Child
,c.Ancestor
FROM #tab t
JOIN cte c
ON t.Father = c.Child
)
SELECT Father = c.Ancestor
,c.Path
FROM cte c
LEFT JOIN cte c2
ON c2.Path LIKE c.Path + ' -> ' + '%'
WHERE c2.Path IS NULL;
的 LiveDemo
强>
这是递归CTE的经典用法,添加了两个字段:从祖先到叶子的Path
和Ancestor
。然后,您需要自我加入结果并使用LIKE
根据Path
过滤出仅仅叶子节点。