Stuff和'For Xml Path'在Sql Server中是如何工作的

Pun*_*wla 320 sql database sql-server

表是:

+----+------+
| Id | Name |
+----+------+    
| 1  | aaa  |
| 1  | bbb  |
| 1  | ccc  |
| 1  | ddd  |
| 1  | eee  |
+----+------+
Run Code Online (Sandbox Code Playgroud)

所需输出:

+----+---------------------+
| Id |        abc          |
+----+---------------------+ 
|  1 | aaa,bbb,ccc,ddd,eee |
+----+---------------------+
Run Code Online (Sandbox Code Playgroud)

查询:

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

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

我很难理解这一点.

Fut*_*Fan 613

下面是它的工作原理:

1.使用FOR XML获取XML元素字符串

将FOR XML PATH添加到查询末尾允许您将查询结果作为XML元素输出,其中元素名称包含在PATH参数中.例如,如果我们要运行以下语句:

SELECT ',' + name 
              FROM temp1
              FOR XML PATH ('')
Run Code Online (Sandbox Code Playgroud)

通过传入一个空字符串(FOR XML PATH('')),我们得到以下内容:

,aaa,bbb,ccc,ddd,eee
Run Code Online (Sandbox Code Playgroud)

2.删除STUFF的前导逗号

STUFF语句实际上将一个字符串"填充"到另一个字符串中,替换第一个字符串中的字符.但是,我们只是使用它来删除结果值列表的第一个字符.

SELECT abc = STUFF((
            SELECT ',' + NAME
            FROM temp1
            FOR XML PATH('')
            ), 1, 1, '')
FROM temp1
Run Code Online (Sandbox Code Playgroud)

参数STUFF是:

  • 字符串被"填充"(在我们的例子中是带有前导逗号的完整名称列表)
  • 开始删除和插入字符的位置(1,我们填入空白字符串)
  • 要删除的字符数(1,是主要逗号)

所以我们最终得到:

aaa,bbb,ccc,ddd,eee
Run Code Online (Sandbox Code Playgroud)

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;
Run Code Online (Sandbox Code Playgroud)

我们得到了结果:

-----------------------------------
| Id        | Name                |
|---------------------------------|
| 1         | aaa,bbb,ccc,ddd,eee |
-----------------------------------
Run Code Online (Sandbox Code Playgroud)

希望这可以帮助!

  • @Fandango68,@ FutbolFan - 他无法为微软的文档团队工作.他的解释太清楚,太直接.;-) (43认同)
  • 你应该为微软的文档团队工作(如果有的话) (37认同)
  • 好答案。令我惊讶的是,直到 2017 年添加了 String_Agg 函数(/sf/answers/3007715091/),微软才提出更方便的组合字符串的方法。我发现 stuff / xml 路径是一种非常冗长/麻烦的方法。 (7认同)
  • @ChrisProsser 我同意。通过在 Oracle 11gR2 中引入“LISTAGG”功能,Oracle 在这方面领先于微软。在我不得不使用它的日子里,我确实想念那个功能。https://www.techonthenet.com/oracle/functions/listagg.php (3认同)
  • 你好。在第1步中,如果您这样做:从temp1 FOR XML PATH('')中选择名称... ...得到<name> aaa </ name> <name> bbb </ name> ... etc ...我没有首先要意识到这一点...将其更改为SELECT''+ name ... etc ...会删除标签。 (2认同)
  • @ChrisProsser - Sybase ASA 几十年来一直有一个“列表”功能。不幸的是,Microsoft 将 SQLServer 基于 Sybase 的 ASE,并且直到去年才开始关注列表函数。我同意 - 这令人难以置信。然后他们做了,他们称之为`string_agg`。我原以为`list`很明显。 (2认同)
  • 我只想说,我现在可能已经引用了这个答案 15 次,最后只是将示例复制到我的剪辑中。谢谢@FutbolFan (2认同)
  • 只是想知道在哪里添加生成的计算字段(而不是普通字段)显然不会在值周围创建 xml 包装器... (2认同)
  • 感谢您对 STUFF 函数的精彩解释,我在其他地方找不到像这样清晰易懂的文档。 (2认同)

Ric*_*ing 67

本文介绍了在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
Run Code Online (Sandbox Code Playgroud)

要了解发生了什么,请从内部查询开始:

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

因为您正在指定FOR XML,所以您将获得包含表示所有行的XML片段的单行.

因为您没有为第一列指定列别名,所以每行都将包装在XML元素中,并在后面的括号中指定名称FOR XML PATH.例如,如果有FOR XML PATH ('X'),您将获得一个类似于以下内容的XML文档:

<X>,aaa</X>
<X>,bbb</X>
...
Run Code Online (Sandbox Code Playgroud)

但是,由于您尚未指定元素名称,因此您只需获取值列表:

,aaa,bbb,...
Run Code Online (Sandbox Code Playgroud)

.value('.', 'varchar(max)')只是从生成的XML片段中检索值,而不对任何"特殊"字符进行XML编码.你现在有一个看起来像这样的字符串:

',aaa,bbb,...'
Run Code Online (Sandbox Code Playgroud)

STUFF函数然后删除前导逗号,给出最终结果,如下所示:

'aaa,bbb,...'
Run Code Online (Sandbox Code Playgroud)

乍一看看起来很混乱,但与其他一些选项相比,它确实表现得相当不错.

  • @PuneetChawla:[`TYPE`指令](https://msdn.microsoft.com/en-us/library/ms190025.aspx)告诉SQL使用`xml`类型返回数据.没有它,数据将返回为`nvarchar(max)`.如果`name`列中有特殊字符,这里用它来避免XML编码问题. (8认同)
  • 在查询中使用Type有什么用.我认为它用于定义,XML路径的结果将存储在值中(不确定如果错误则解释它). (2认同)
  • @barlop:正如[SimpleTalk文章](https://www.red-gate.com/simple-talk/sql/t-sql-programming/concatenating-row-values-in-transact-sql/)解释的那样,如果你删除`TYPE`和`.value('.','varchar(max)')`,然后你可以在结果中得到XML编码的实体. (2认同)
  • @RichardDeeming 你的意思是数据是否包含或可能包含尖括号? (2认同)
  • _但是,由于您没有指定元素名称,所以您只是得到一个值列表_,这是我所缺少的见解。谢谢。 (2认同)

Neh*_*pra 39

PATH模式用于从SELECT查询生成XML

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>
Run Code Online (Sandbox Code Playgroud)

Output是以元素为中心的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>
Run Code Online (Sandbox Code Playgroud)

对于步骤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
Run Code Online (Sandbox Code Playgroud)

在第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
Run Code Online (Sandbox Code Playgroud)

在第6步中,我们按ID对日期进行分组.

STUFF(source_string,start,length,add_string)参数或参数source_string要修改的源字符串.start source_string中的位置以删除长度字符,然后插入add_string.length要从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 |
-----------------------------------
Run Code Online (Sandbox Code Playgroud)

  • 对于任何想知道第 4 点以及为什么 &lt;Name&gt; 消失的人。这是因为用逗号连接 Name 后不再有列而只有值,所以 SQL Server 不知道应该使用什么名称的 xml 标记。例如这个查询`SELECT 'a' FROM some_table FOR XML PATH('')`将产生:`'aaaaaaa'`。但是如果指定列名:`SELECT 'a' AS Col FROM some_table FOR XML PATH('')` 你会得到结果:`&lt;Col&gt;a&lt;/Col&gt;&lt;Col&gt;a&lt;/Col&gt;&lt;Col&gt;a &lt;/Col&gt;` (7认同)
  • 您写道“在第 4 步中,我们正在连接值。” 但不清楚为什么/如何将 `','` 指定为列,与 xml 路径后的 `('')` 结合,导致连接发生 (3认同)

Bri*_*den 17

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

select id, STRING_AGG(name, ',') as abc
from temp1
group by id
Run Code Online (Sandbox Code Playgroud)

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

编辑:当我最初发布这个时,我提到了SQL Server 2016,因为我认为我看到了一个潜在的功能,包括在内.要么我记得错误或改变了某些东西,感谢建议编辑修复版本.此外,令人印象深刻,并没有充分意识到多步审查过程,只是拉我进入最后的选择.

  • STRING_AGG不在SQL Server 2016中.据说它将出现在"vNext"中. (2认同)

小智 5

在中for xml path,如果我们定义任何类似的值,[ for xml path('ENVLOPE') ]则这些标记将与每一行一起添加:

<ENVLOPE>
</ENVLOPE>
Run Code Online (Sandbox Code Playgroud)