12

我在 google 的帮助下编写了这个查询,以从表中创建一个分隔列表,但我对此查询一无所知。

谁能解释一下发生了什么

 SELECT 
    E1.deptno, 
    allemp = Replace ((SELECT E2.ename AS 'data()' 
                       FROM emp AS e2 
                       WHERE e1.deptno = e2.DEPTNO 
                       FOR xml PATH('')), ' ', ', ') 
 FROM EMP AS e1 
 GROUP BY DEPTNO; 

给我结果

10  CLARK, KING, MILLER
20  SMITH, JONES, SCOTT, ADAMS, FORD
30  ALLEN, WARD, MARTIN, BLAKE, TURNER, JAMES
4

4 回答 4

38

解释它的最简单方法是查看FOR XML PATH实际 XML 的工作原理。想象一个简单的表格Employee

EmployeeID      Name
1               John Smith
2               Jane Doe

你可以使用

SELECT  EmployeeID, Name
FROM    emp.Employee
FOR XML PATH ('Employee')

这将创建如下 XML

<Employee>
    <EmployeeID>1</EmployeeID>
    <Name>John Smith</Name>
</Employee>
<Employee>
    <EmployeeID>2</EmployeeID>
    <Name>Jane Doe</Name>
</Employee>

删除 'Employee' fromPATH会删除外部 xml 标记,因此此查询:

SELECT  Name
FROM    Employee
FOR XML PATH ('')

会创建

    <Name>John Smith</Name>
    <Name>Jane Doe</Name>

您所做的并不理想,列名“data()”会强制出现 sql 错误,因为它正在尝试创建一个不是合法标签的 xml 标签,因此会生成以下错误:

列名“Data()”包含 FOR XML 要求的无效 XML 标识符;'('(0x0028) 是第一个错误字符。

相关子查询隐藏了这个错误,只生成了没有标签的 XML:

SELECT  Name AS [Data()]
FROM    Employee
FOR XML PATH ('')

创建

John Smith Jane Doe

然后你用逗号替换空格,相当不言自明......

如果我是你,我会稍微调整一下查询:

SELECT  E1.deptno, 
        STUFF(( SELECT  ', ' + E2.ename 
                FROM    emp AS e2 
                WHERE   e1.deptno = e2.DEPTNO 
                FOR XML PATH('')
            ), 1, 2, '') 
FROM    EMP AS e1 
GROUP BY DEPTNO; 

没有列别名将意味着没有创建 xml 标记,并且在选择查询中添加逗号意味着任何带有空格的名称都不会导致错误,STUFF将删除第一个逗号和空格。

附录

为了详细说明 KM 在评论中所说的话,因为这似乎得到了更多的意见,转义 XML 字符的正确方法是使用.value如下:

SELECT  E1.deptno, 
        STUFF(( SELECT  ', ' + E2.ename 
                FROM    emp AS e2 
                WHERE   e1.deptno = e2.DEPTNO 
                FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)'), 1, 2, '') 
FROM    EMP AS e1 
GROUP BY DEPTNO; 
于 2012-04-30T10:15:37.247 回答
6

一步一步地把它拆开——从里到外。

第1步:

运行最里面的查询,看看它产生了什么:

SELECT E2.ename AS 'data()' 
FROM emp AS e2 
WHERE e2.DEPTNO = 10
FOR XML PATH('')

你应该得到类似的输出:

CLARK KING MILLER

第2步:

只是将REPLACE空格替换为,- 从而将您的输出变为

CLARK, KING, MILLER

第 3 步:

外部查询获取deptno值 - 加上内部查询的结果 - 并产生最终结果。

于 2012-04-30T09:49:39.723 回答
1

SQL Server 2017使用STRING_AGG的. 最近看到这篇文章,并切换了我的STUFF/FOR XML策略以使用新的字符串函数。也避免了额外的 JOIN/SUBQUERY 和 FOR XML 的开销(以及奇怪的编码问题)和难以解释的 SQL。

SELECT  E1.deptno, 
        STRING_AGG(E1.ename, ', ') AS allemp
FROM    EMP AS e1 
GROUP BY DEPTNO; 

注意:还请务必查看对应STRING_SPLIT的内容,以便更轻松地处理 SQL 分隔数据。

于 2017-09-30T11:41:09.253 回答
0

外部查询检索部门编号列表,然后为每个部门编号运行子查询以返回属于该部门的所有名称。子查询使用 FOR XML 语句将输出格式化为单行逗号分隔列表。

于 2012-04-30T09:45:32.810 回答