2015-07-03 77 views
150

表是:怎样的东西, '对于XML路径' 在SQL服务器工作

+----+------+ 
| Id | Name | 
+----+------+  
| 1 | aaa | 
| 1 | bbb | 
| 1 | ccc | 
| 1 | ddd | 
| 1 | eee | 
+----+------+ 

需要的输出:

+----+---------------------+ 
| Id |  abc   | 
+----+---------------------+ 
| 1 | aaa,bbb,ccc,ddd,eee | 
+----+---------------------+ 

查询:

SELECT ID, 
    abc = STUFF(
       (SELECT ',' + name FROM temp1 FOR XML PATH ('')), 1, 1, '' 
       ) 
FROM temp1 GROUP BY id 

此查询工作正常。但我只需要解释它是如何工作的,或者是否有任何其他或简短的方法来做到这一点。

我越来越糊涂了解这一点。

+0

参见https://stackoverflow.com/questions/21623593/what-is-the-meaning-of-select-for-xml-path-1-1 – ChrisF

+1

令人难以置信的是,我开始了一个错误的问题的赏金,哈哈。 – user798719

+0

我为此做了一个[SqlFiddle页面](http://sqlfiddle.com/#!18/442cc/4),看它在现实生活中的工作。希望它能帮助别人。 – Sabuncu

回答

296

下面是它的工作原理:用FOR XML

添加FOR XML PATH到查询的末尾

1.使用XML元素字符串可以让你输出的查询作为XML的结果元素,包含在PATH参数中的元素名称。例如,如果我们运行下面的语句:

SELECT ',' + name 
       FROM temp1 
       FOR XML PATH ('') 

通过在一个空字符串传递(FOR XML PATH( ''))来代替,我们得到如下:

,aaa,bbb,ccc,ddd,eee 

2 。删除带有STUFF的前导逗号

STUFF语句将字符串“填充”到另一个字符串中,替换第一个字符串中的字符,然而,我们只是用它来删除结果列表的第一个字符。

SELECT abc = STUFF((
      SELECT ',' + NAME 
      FROM temp1 
      FOR XML PATH('') 
      ), 1, 1, '') 
FROM temp1 

STUFF的参数是:

  • 被“塞”(在本例中的名称与 领先逗号的完整列表)的字符串
  • 开始删除的位置,插入字符(1,我们填入空白字符串)
  • 要删除的字符数(1,作为前导逗号)

所以我们最终得到:

aaa,bbb,ccc,ddd,eee 

3. ID注册以获得完整列表

接下来,我们刚刚加入这个ID的临时表就行了,拿到名单与名字的ID:

SELECT ID, abc = STUFF(
      (SELECT ',' + name 
       FROM temp1 t1 
       WHERE t1.id = t2.id 
       FOR XML PATH ('')) 
      , 1, 1, '') from temp1 t2 
group by id; 

,我们有我们的结果:

----------------------------------- 
| Id  | Name    | 
|---------------------------------| 
| 1   | aaa,bbb,ccc,ddd,eee | 
----------------------------------- 

希望这有助于!

+8

嗨尼泊尔,谢谢你的回答。我得到了完整的解释。 –

+8

真的很好的解释! –

+6

对我很有帮助+1 –

45

This article涵盖了在SQL中连接字符串的各种方式,包括代码的改进版本,它不对连接值进行XML编码。

SELECT ID, abc = STUFF 
(
    (
     SELECT ',' + name 
     FROM temp1 As T2 
     -- You only want to combine rows for a single ID here: 
     WHERE T2.ID = T1.ID 
     ORDER BY name 
     FOR XML PATH (''), TYPE 
    ).value('.', 'varchar(max)') 
, 1, 1, '') 
FROM temp1 As T1 
GROUP BY id 

要了解发生了什么,与内查询开始:

SELECT ',' + name 
FROM temp1 As T2 
WHERE T2.ID = 42 -- Pick a random ID from the table 
ORDER BY name 
FOR XML PATH (''), TYPE 

因为你指定FOR XML,你会得到一个包含占所有行的XML片段单行。

由于您没有为第一列指定列别名,因此每行都将被包装在一个XML元素中,其名称在FOR XML PATH之后的括号内指定。例如,如果你有FOR XML PATH ('X'),你会得到一个看起来像一个XML文档:

<X>,aaa</X> 
<X>,bbb</X> 
... 

但是,因为你还没有指定的元素名称,你只会得到的值的列表:

,aaa,bbb,... 

.value('.', 'varchar(max)')只是从结果XML片段中检索值,而不对任何“特殊”字符进行XML编码。您现在有一个字符串,它看起来像:

​​

STUFF功能,然后删除前导逗号,给你一个最后的结果是这样的:

'aaa,bbb,...' 

它乍看上去相当混乱,但与其他一些选择相比,它的表现往往非常好。

+0

嗨理查德,谢谢你的回答。这有点混乱,但我几乎得到完整的解释和逻辑。 –

+2

类型在你的查询中有什么用处。我认为它的定义,XML路径的结果将存储在值(不知道如果错误解释它)。 –

+7

@PuneetChawla:['TYPE'指令](https://msdn.microsoft.com/en-us/library/ms190025.aspx)告诉SQL使用'xml'类型返回数据。没有它,数据将以'nvarchar(max)'的形式返回。如果'name'列中有特殊字符,它在这里用来避免XML编码问题。 –

1

在用于XML路径 如果我们定义等[用于XML路径( 'ENVLOPE')]然后将与各行被添加

上述标签的任何值。

+0

非常感谢。我正在使用FOR XML PATH解决方案。 – FrenkyB

6

Azure SQL数据库和SQL Server中有非常新的功能(从2017开始)来处理这种确切的情况。我相信这将作为您尝试使用XML/STUFF方法完成的本地官方方法。例如:

select id, STRING_AGG(name, ',') as abc 
from temp1 
group by id 

STRING_AGG - https://msdn.microsoft.com/en-us/library/mt790580.aspx

编辑:当我最初发布这个我做的SQL Server 2016作为提及我以为我看到了一个潜在的功能,它是被包括在内。要么我记得那个不正确的东西或改变了什么,感谢修改版本的建议编辑。此外,相当深刻的印象,并没有完全意识到多步审查过程,刚刚拉我最后的选择。

+2

STRING_AGG不在SQL Server 2016中。据说它将进入“vNext”。 – N8allan

+0

糟糕,我不是故意重写@lostmylogin的编辑,抱歉...这是谁通过修正编辑实际推送的。 –

0
Declare @Temp As Table (Id Int,Name Varchar(100)) 
Insert Into @Temp values(1,'A'),(1,'B'),(1,'C'),(2,'D'),(2,'E'),(3,'F'),(3,'G'),(3,'H'),(4,'I'),(5,'J'),(5,'K') 
Select X.ID, 
stuff((Select ','+ Z.Name from @Temp Z Where X.Id =Z.Id For XML Path('')),1,1,'') 
from @Temp X 
Group by X.ID 
0
SELECT ID, 
    abc = STUFF(
       (SELECT ',' + name FROM temp1 FOR XML PATH ('')), 1, 1, '' 
       ) 
FROM temp1 GROUP BY id 

在这里,在上面的查询STUFF功能用于只去除第一逗号(,)从所生成的XML字符串(,aaa,bbb,ccc,ddd,eee)然后它会成为(aaa,bbb,ccc,ddd,eee)

而且FOR XML PATH('')只是列数据转换成字符串(,aaa,bbb,ccc,ddd,eee)但在PATH我们传递“”,所以它不会创建一个XML标签。

最后,我们使用ID列对分组记录进行分组。

16

PATH模式中生成XML使用来自SELECT查询

1. SELECT 
     ID, 
     Name 
FROM temp1 
FOR XML PATH; 

Ouput: 
<row> 
<ID>1</ID> 
<Name>aaa</Name> 
</row> 

<row> 
<ID>1</ID> 
<Name>bbb</Name> 
</row> 

<row> 
<ID>1</ID> 
<Name>ccc</Name> 
</row> 

<row> 
<ID>1</ID> 
<Name>ddd</Name> 
</row> 

<row> 
<ID>1</ID> 
<Name>eee</Name> 
</row> 

的输出元件为中心的XML,其中在所得到的行集中每个列的值被包裹在一个行元素。由于SELECT子句未为列名指定任何别名,因此生成的子元素名称与SELECT子句中的相应列名称相同。

对于行集中的每一行添加一个标签。

2. 
SELECT 
     ID, 
     Name 
FROM temp1 
FOR XML PATH(''); 

Ouput: 
<ID>1</ID> 
<Name>aaa</Name> 
<ID>1</ID> 
<Name>bbb</Name> 
<ID>1</ID> 
<Name>ccc</Name> 
<ID>1</ID> 
<Name>ddd</Name> 
<ID>1</ID> 
<Name>eee</Name> 

对于第2步:如果您指定一个零长度的字符串,则不生成包装元素。

3. 

    SELECT 

      Name 
    FROM temp1 
    FOR XML PATH(''); 

    Ouput: 
    <Name>aaa</Name> 
    <Name>bbb</Name> 
    <Name>ccc</Name> 
    <Name>ddd</Name> 
    <Name>eee</Name> 

4. SELECT 
     ',' +Name 
FROM temp1 
FOR XML PATH('') 

Ouput: 
,aaa,bbb,ccc,ddd,eee 

在步骤4中,我们将值连接起来。

5. SELECT ID, 
    abc = (SELECT 
      ',' +Name 
    FROM temp1 
    FOR XML PATH('')) 
FROM temp1 

Ouput: 
1 ,aaa,bbb,ccc,ddd,eee 
1 ,aaa,bbb,ccc,ddd,eee 
1 ,aaa,bbb,ccc,ddd,eee 
1 ,aaa,bbb,ccc,ddd,eee 
1 ,aaa,bbb,ccc,ddd,eee 


6. SELECT ID, 
    abc = (SELECT 
      ',' +Name 
    FROM temp1 
    FOR XML PATH('')) 
FROM temp1 GROUP by iD 

Ouput: 
ID abc 
1 ,aaa,bbb,ccc,ddd,eee 

在步骤6中,我们将ID按日期分组。

STUFF(source_string,启动,长度,add_string) 参数或参数 source_string 源字符串进行修改。 开始 在source_string中删除长度字符的位置,然后插入add_string。 长度 要从source_string中删除的字符数。 add_string 在起始位置插入source_string的字符序列。

SELECT ID, 
    abc = 
    STUFF (
     (SELECT 
       ',' +Name 
     FROM temp1 
     FOR XML PATH('')), 1, 1, '' 
    ) 
FROM temp1 GROUP by iD 

Output: 
----------------------------------- 
| Id  | Name    | 
|---------------------------------| 
| 1   | aaa,bbb,ccc,ddd,eee | 
----------------------------------- 
+0

非常好的解释 – user798719

0

我做了调试,终于回到我的“酿”查询到它,它的正常方式。

只需

select * from myTable for xml path('myTable') 

给我的表的内容从触发我调试写入日志表。