【问题标题】:How Stuff and 'For Xml Path' work in SQL Server?Stuff 和“For Xml Path”在 SQL Server 中如何工作?
【发布时间】:2015-09-21 13:44:48
【问题描述】:

表格是:

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

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

我对此感到非常困惑。

【问题讨论】:

  • 我为此做了一个SqlFiddle page,看看它在现实生活中的作用。希望对其他人有所帮助。
  • ^也许ID在不同实体的不同表中是唯一的,而这个表是存储属于它们的东西。
  • 如果某些行具有不同的 ID,则此查询不起作用。例如如果 'ddd' 和 'eee' 的 ID 为 2。
  • 我每月访问此页面的时间,看看我哪里出错了。

标签: sql sql-server for-xml-path stuff


【解决方案1】:

这是它的工作原理:

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

将 FOR XML PATH 添加到查询末尾允许您将查询结果作为 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

【讨论】:

  • 你应该为微软的文档团队工作(如果有的话)
  • @Fandango68 ,@FutbolFan - 他不能为微软的文档团队工作。他的解释太清楚太直接了。 ;-)
  • 好答案。令我惊讶的是,直到 2017 年添加了 String_Agg 函数(stackoverflow.com/a/42967358/2012446),微软才提供了一种更方便的方式来组合字符串。我发现 stuff / xml path 是一种非常冗长/繁琐的方法。
  • @ChrisProsser 我同意。通过在 Oracle 11gR2 中引入 LISTAGG 函数,Oracle 在这方面领先于微软。在我不得不使用它的日子里,我确实错过了这个功能。 techonthenet.com/oracle/functions/listagg.php
  • 你好。在第 1 步中,如果你这样做: SELECT name FROM temp1 FOR XML PATH ('') ...你会得到 aaabbb ...etc...我没有'起初没有意识到这一点...将其更改为 SELECT ''+name ...etc... 删除标签。
【解决方案2】:

STUFF((SELECT distinct ',' + CAST(T.ID) FROM Table T where T.ID= 1 FOR XML PATH('')),1,1,'') AS Name

【讨论】:

    【解决方案3】:

    for xml path 中,如果我们定义像[ for xml path('ENVLOPE') ] 这样的任何值,那么这些标签将被添加到每一行中:

    <ENVLOPE>
    </ENVLOPE>
    

    【讨论】:

      【解决方案4】:

      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,因为我认为我看到了将要包含的潜在功能。要么我记错了,要么发生了一些变化,感谢修复版本的建议编辑。此外,给我留下了深刻的印象,但并没有完全意识到多步审核过程只是让我选择了最终选择。

      【讨论】:

      • STRING_AGG 不在 SQL Server 2016 中。据说在“vNext”中。
      • 糟糕,我不是故意要覆盖来自@lostmylogin 的编辑,对此很抱歉……那才是真正推动更正编辑的人。
      【解决方案5】:

      我进行了调试,最后以正常方式返回了我的“填充”查询。

      简单

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

      将表的内容从我调试的触发器中写入日志表。

      【讨论】:

        【解决方案6】:

        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>
        

        输出是以元素为中心的 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, start, length, add_string) 参数或参数 源字符串 要修改的源字符串。 开始 在source_string 中的位置删除length 个字符然后插入add_string。 长度 要从 source_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 |
        -----------------------------------
        

        【讨论】:

        • 您写道“在第 4 步中,我们正在连接这些值。”但尚不清楚为什么/如何将 ',' 指定为列,与 xml 路径后的 ('') 结合,导致连接发生
        • 在第 4 步中,执行任何字符串操作都将使用指定的包装元素,在这种情况下为空白 ('')。
        • 对于任何想知道第 4 点以及为什么 消失的人。这是因为用逗号连接名称后不再有列,而只有值,所以 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】:
        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 列对记录进行了分组。

        【讨论】:

          【解决方案8】:
          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
          

          【讨论】:

            【解决方案9】:

            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 编码。你现在有一个看起来像这样的字符串:

            ',aaa,bbb,...'
            

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

            'aaa,bbb,...'
            

            乍一看,它看起来很混乱,但与其他一些选项相比,它的性能确实相当不错。

            【讨论】:

            • 在您的查询中输入类型有什么用。我认为它用于定义,XML路径的结果将存储在值中(如果错误不确定解释)。
            • @PuneetChawla: The TYPE directive 告诉 SQL 使用 xml 类型返回数据。没有它,数据将作为nvarchar(max) 返回。如果name 列中有特殊字符,则在此处使用它来避免 XML 编码问题。
            • @barlop:正如the SimpleTalk article 解释的那样,如果您删除TYPE.value('.', 'varchar(max)'),那么您最终会在结果中得到XML 编码的实体。
            • @RichardDeeming 你的意思是数据是否包含或可能包含尖括号?
            • 但是,由于您没有指定元素名称,您只会得到一个值列表,这就是我所缺少的洞察力。谢谢。
            猜你喜欢
            • 1970-01-01
            • 2012-12-16
            • 2017-07-06
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多