使用SQL将XML结构转置/展平为列

Dav*_*.id 7 xml t-sql sql-server pivot xquery-sql

我正在使用SQL Server(2008/2012),我知道很多搜索都有类似的答案,但我似乎无法为我的案例找到合适的示例/指针.

我在SQL Server表中有一个XML列来保存这些数据:

<Items>
 <Item>
  <FormItem>
    <Text>FirstName</Text>
    <Value>My First Name</Value>
  </FormItem>
  <FormItem>
    <Text>LastName</Text>
    <Value>My Last Name</Value>
  </FormItem>
  <FormItem>
    <Text>Age</Text>
    <Value>39</Value>
  </FormItem>
 </Item>
 <Item>
  <FormItem>
    <Text>FirstName</Text>
    <Value>My First Name 2</Value>
  </FormItem>
  <FormItem>
    <Text>LastName</Text>
    <Value>My Last Name 2</Value>
  </FormItem>
  <FormItem>
    <Text>Age</Text>
    <Value>40</Value>
  </FormItem>
 </Item>
</Items>
Run Code Online (Sandbox Code Playgroud)

因此,即使结构<FormItem>相同,我也可以拥有多个(通常不超过20-30个)表单项.

我本质上是尝试以下面的格式从SQL返回一个查询,即基于/ FormItem/Text的动态列:

FirstName         LastName         Age    ---> More columns as new `<FormItem>` are returned
My First Name     My Last Name     39          Whatever value etc..
My First Name 2   My Last Name 2   40          
Run Code Online (Sandbox Code Playgroud)

所以,目前我有以下内容:

select 
    Tab.Col.value('Text[1]','nvarchar(100)') as Question,
    Tab.Col.value('Value[1]','nvarchar(100)') as Answer
from
    @Questions.nodes('/Items/Item/FormItem') Tab(Col)
Run Code Online (Sandbox Code Playgroud)

当然,这还没有调换我的XML行转换成列,显然是无论如何固定领域..我一直在尝试不同的"动态SQL"的方法,其中SQL执行的不同的选择(对我来说)的<Text>节点,然后使用某种Pivot?但我似乎无法找到神奇的组合来返回我需要的结果作为每行(<Item>在集合中<Items>)的动态列集.

我确信看到这么多非常相似的例子可以做到,但是我的解决方案再次出现了!

任何帮助感激不尽!

Mik*_*son 7

解析XML相当昂贵,因此不是解析一次来构建动态查询,而是一次获取数据,您可以创建一个带有Name-Value列表的临时表,然后将其用作动态数据透视查询的源.
dense_rank是否有创建ID来转动.
要在动态查询中构建列列表,它将使用该for xml path('')技巧.

此解决方案要求您的表具有主键(ID).如果您在变量中包含XML,则可以稍微简化一下.

select dense_rank() over(order by ID, I.N) as ID,
       F.N.value('(Text/text())[1]', 'varchar(max)') as Name,
       F.N.value('(Value/text())[1]', 'varchar(max)') as Value
into #T
from YourTable as T
  cross apply T.XMLCol.nodes('/Items/Item') as I(N)
  cross apply I.N.nodes('FormItem') as F(N)

declare @SQL nvarchar(max)
declare @Col nvarchar(max)

select @Col = 
  (
  select distinct ','+quotename(Name)
  from #T
  for xml path(''), type
  ).value('substring(text()[1], 2)', 'nvarchar(max)')

set @SQL = 'select '+@Col+'
            from #T
            pivot (max(Value) for Name in ('+@Col+')) as P'

exec (@SQL)

drop table #T
Run Code Online (Sandbox Code Playgroud)

SQL小提琴