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

时间:2015-07-03 17:03:05

标签: sql sql-server database

表是:

+----+------+
| 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

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

我很难理解这一点。

10 个答案:

答案 0 :(得分:541)

以下是它的工作原理:

<强> 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的参数是:

  • 要填充的字符串(在我们的例子中是带有a的完整名称列表) 领先的逗号)
  • 开始删除和插入字符的位置(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 |
-----------------------------------

希望这有帮助!

答案 1 :(得分:62)

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片段中检索值,而不对任何&#34;特殊&#34;进行XML编码。字符。你现在有一个看起来像这样的字符串:

',aaa,bbb,...'

STUFF函数会删除前导逗号,为您提供如下所示的最终结果:

'aaa,bbb,...'

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

答案 2 :(得分:31)

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>

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>

对于步骤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 要修改的源字符串。 开始 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 |
-----------------------------------

答案 3 :(得分:16)

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,因为我认为我已经看到了一个潜在的功能。要么我记得错误或改变了某些东西,感谢建议编辑修复版本。此外,非常令人印象深刻,并没有充分意识到多步审查过程只是让我进入最后的选择。

答案 4 :(得分:3)

public class CommonDAOImpl{ private SessionFactory sessionFactoryCommon; public void setSessionFactoryCommon(SessionFactory sessionFactoryCommon) { this.sessionFactoryCommon = sessionFactoryCommon; System.out.println("SessionFactory is set"); } public void addMoney(String userId,double amount) { Session session = this.sessionFactoryCommon.openSession(); try{ String hqlQuery = "HQL query to update Amount Table where userId = userId"; session.createQuery(hqlQuery).executeUpdate(); } catch(Exception ex) { ex.printStackTrace(); } finally { session.close(); } } public void transferMoney(String fromUserId, String toUserId, double amount) { Session session = null; try { session = this.sessionFactoryCommon.openSession(); query = "HQL query to get previous amount from sender account"; double prevSenderamount = (double) query.uniqueResult(); if(prevAmount>=amount) { String query1 = "HQL query to update sender account"; session.createQuery(hqlQuery).executeUpdate(); String query1 = "HQL query to get previous amount from receiver account"; double prevReceiveramount = (double) query.uniqueResult(); String query3 = "HQL query to update receiver account"; session.createQuery(hqlQuery).executeUpdate(); } else { throw myException("Insufficient Amount"); } System.out.println("1. Employee save called without transaction, id="+id); } catch(Exception ex) { System.out.println("Exception Block"); ex.printStackTrace(); } finally { session.close(); } } } 中,如果我们定义for xml path之类的任何值,那么这些标记将随每行添加:

[ for xml path('ENVLOPE') ]

答案 5 :(得分:2)

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)字符串,但在路径中我们正在传递&#39;&#39;所以它不会创建XML标记。

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

答案 6 :(得分:1)

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

答案 7 :(得分:1)

我做了调试,最后以正常的方式返回了我的'填充'查询。

简单

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

给我表格的内容,以便从我调试的触发器写入日志表。

答案 8 :(得分:-2)

我经常与where子句一起使用

SELECT 
TapuAda=STUFF(( 
SELECT ','+TBL.TapuAda FROM (
SELECT TapuAda FROM T_GayrimenkulDetay AS GD 
INNER JOIN dbo.T_AktiviteGayrimenkul AS AG ON  D.GayrimenkulID=AG.GayrimenkulID WHERE 
AG.AktiviteID=262
) AS TBL FOR XML PATH ('')
),1,1,'')

答案 9 :(得分:-2)

STUFF((从表T中选择不同的','+ CAST(T.ID),其中T.ID = 1 FOR XML PATH('')),1,1,'')AS名称