SQL 服务器 FOR XML 路径使重复节点

SQL Server FOR XML Path make repeating nodes

我想使用 SQL Server 2012 生成以下输出:

<parent>
  <item>1</item>
  <item>2</item>
  <item>3</item>
</parent>

来自同一 table 中的三个不同列(我们称它们为 col1、col2 和 col3)。

我正在尝试使用此查询:

SELECT 
  t.col1 as 'item'
 ,t.col2 as 'item'
 ,t.col3 as 'item' 
FROM tbl t 
FOR XML PATH('parent'), TYPE

但我得到的是:

<parent>
  <item>123</item>
</parent>

我做错了什么?

我认为如果您像这样更改列的别名它应该可以工作。这是因为别名相同并且数据的数据类型也可能相同。如果您在 col1、col2 和 col3 中有不同的数据,则不应显示此行为。

SELECT 
t.col1 as 'item'
,t.col2 as 'item1'
,t.col3 as 'item2' 
FROM tbl t 
FOR XML PATH('parent'), TYPE

好的,您不能为此使用路径。相反,使用显式

SELECT 1 AS tag,NULL AS parent, t.col1 AS [Parent!1!Item!element],
               t.col2 AS [Parent!1!Item!element],
               t.col3 AS [Parent!1!Item!element]
FROM tbl t
FOR XML EXPLICIT

实际上有几种方法可以使用 XML 路径语法来解决这个问题。

首先是先对结果进行 UNPIVOT,例如:

SELECT item as [text()]
FROM 
   (select col1, col2, col3 from tbl) p
UNPIVOT
   (item FOR colHeading IN (col1, col2, col3)) AS unpvt
FOR XML PATH ('item'), ROOT ('parent')

第二个不需要逆轴,但会重复您的更多查询:

select (select col1 as [text()] from tbl for xml path('item'), type)
    ,  (select col2 as [text()] from tbl for xml path('item'), type)
    ,  (select col3 as [text()] from tbl for xml path('item'), type)
for xml path ('parent')

这两个都会将多行数据合并到同一个父节点下。例如,如果您有 2 行,第一行是 1,2,3,第二行是 4,5,6,您会得到:

<parent>
    <item>1</item>
    <item>2</item>
    <item>3</item>
    <item>4</item>
    <item>5</item>
    <item>6</item>
</parent>

相反,如果您希望逆透视的每一行每行都有一个唯一的父元素,那么,假设每一行都有一些行标识符(我称之为 parentId),您可以将它们分组通过调整这些方法来排行:

SELECT
  (
      SELECT item as [text()]
      FROM 
        (select parentId, col1, col2, col3 from tbl tt where tt.parentid =   t.parentid) p
      UNPIVOT
        (item FOR colHeading IN (col1, col2, col3)) AS unpvt
      FOR XML PATH ('item'), TYPE
  )
FROM tbl t
FOR XML PATH ('parent')

select (select col1 as [text()] from tbl tt where t.parentid = tt.parentid for xml path('item'), type)
    ,  (select col2 as [text()] from tbl tt where t.parentid = tt.parentid for xml path('item'), type)
    ,  (select col3 as [text()] from tbl tt where t.parentid = tt.parentid for xml path('item'), type)
from tbl t
for xml path ('parent')

这将导致:

<parent>
    <item>1</item>
    <item>2</item>
    <item>3</item>
</parent>
<parent>
    <item>4</item>
    <item>5</item>
    <item>6</item>
</parent>

Sql Fiddle with demo

这里有几个注意事项:如果你使用 FOR XML EXPLICIT,你不能使用 WITH XMLNAMESPACES(我没有提到这个要求,所以我仍然保留接受的回答)。虽然 Iheria 的回答也很有帮助,但我后来意识到了另一种更简单的可能性:

SELECT CONVERT(XML, '<item>' + t.col1 
           + '</item><item>' + t.col2 
           + '</item><item>' + t.col3 + '</item>')
FROM tbl t
FOR XML PATH('parent'), TYPE

我认为这可能是最简单和最高效的方法(我没有对它进行基准测试,但我无法想象使用 UNPIVOT 会更快,如果有的话,多个 SELECT 选项很可能被引擎重构为这个)。

添加一个值为 NULL 的列,为每一列生成一个单独的项目节点。

SELECT 
  t.col1 as 'item'
 ,NULL
 ,t.col2 as 'item'
 ,NULL
 ,t.col3 as 'item' 
FROM dbo.tbl as t 
FOR XML PATH('parent'), TYPE;

结果:

<parent>
  <item>1</item>
  <item>2</item>
  <item>3</item>
</parent>

SQL Fiddle

为什么这样做?

没有名称的列作为文本节点插入。在这种情况下,NULL 值作为文本节点插入 item 节点之间。

如果您添加实际值而不是 NULL,您将看到发生了什么。

SELECT 
  t.col1 as 'item'
 ,'1'
 ,t.col2 as 'item'
 ,'2'
 ,t.col3 as 'item' 
FROM dbo.tbl as t 
FOR XML PATH('parent'), TYPE;

结果:

<parent>
  <item>1</item>1<item>2</item>2<item>3</item></parent>

另一种指定不带名称的列的方法是使用通配符 * 作为列别名。

Columns with a Name Specified as a Wildcard Character

在这种情况下没有必要使用通配符,因为具有 NULL 值的列没有列名,但是当您需要实际列中的值但不希望列名出现时,它很有用是一个节点名称。