使用 SQL 将 XML 结构转置/展平到列
Posted
技术标签:
【中文标题】使用 SQL 将 XML 结构转置/展平到列【英文标题】:Using SQL to transpose/flatten XML structure to columns 【发布时间】:2013-02-16 05:41:25 【问题描述】:我正在使用 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>
因此,即使<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
所以,此刻我有以下几点:
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)
当然,这并没有将我的 XML 行转换为列,而且显然是用字段修复的。我一直在尝试各种“动态 SQL”方法,其中 SQL 执行不同选择(在我的情况下)@ 987654325@ 节点,然后使用某种 Pivot?但我似乎找不到神奇的组合来返回我需要的结果作为每一行的动态列集(<Items>
集合中的<Item>
)。
看到这么多非常相似的例子,我确信它可以完成,但是我又一次找不到解决方案!
感谢您的帮助!
【问题讨论】:
【参考方案1】:解析 XML 相当昂贵,因此您可以使用名称-值列表创建一个临时表,然后将其用作动态数据透视查询的源,而不是解析一次来构建动态查询和一次获取数据。 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
SQL Fiddle
【讨论】:
谢谢!就像我需要的那样工作..实际上我在所示示例之上还有另一个级别,但这实际上是将select Tab.Col.value('(FormItem[Text = "FirstName"]/Value)[1]', 'varchar(32)') as FirstName,
Tab.Col.value('(FormItem[Text = "LastName"]/Value)[1]', 'varchar(32)') as LastName,
Tab.Col.value('(FormItem[Text = "Age"]/Value)[1]', 'int') as Age
from @Questions.nodes('/Items/Item') Tab(Col)
【讨论】:
谢谢,很好的例子,实际上以前没有见过这种技术,但是它不会将字段动态显示为列 - 如果您事先知道列的数量可能没问题,但是来自@Mikael 的上述示例满足了我的需要 - 不过,感谢您的回复,并且再次确定非常简单的干净示例。【参考方案3】:我想添加我的“自己的答案”真的只是为了完整,可能会帮助其他人。但是它绝对是基于上面@Mikael 的巨大帮助!再说一次,这真的只是为了完整性-@Mikael 的所有荣誉。
基本上我最终得到了以下过程。我需要选择一些数据/过滤器,并获取一些连接数据,并允许对一些输入参数进行一些布尔过滤。然后进入下一部分,通过交叉应用创建我的关系数据和所需 xml 节点的临时表。最后一步是旋转结果/从选定的 XML 节点动态创建列..
CREATE PROCEDURE [dbo].[usp_RPT_ExtractFlattenentries]
@CompanyID int,
@MainSelector nvarchar(50) = null,
@SecondarySelector nvarchar(255) = null,
@DateFrom datetime = '01-jan-2012',
@DateTo datetime = '31-dec-2100',
@SysReference nvarchar(20) = null
AS
BEGIN
SET NOCOUNT ON;
-- Create the table var to hold the XML form data from the entries
declare @FeedbackXml table (
ID int identity primary key,
XMLCol xml,
CompanyName nvarchar(20),
SysReference nvarchar(20),
RecordDate datetime,
EntryName nvarchar(255),
MainSelector nvarchar(50)
)
-- STEP 1: Get the raw submission data based on the params passed in
-- *Note: The double casting is necessary as the "form" field is nvarchar (not varchar) and we need xml in UTF-8 format
begin
insert into @FeedbackXml
(XMLCol, CompanyName, SysReference, RecordDate, EntryName, MainSelector)
select cast(cast(e.form as nvarchar(max)) as xml), c.name, e.SysReference, e.RecordDate, e.name, e.wizard
from
entries s
left join
companies o on e.companies = c.ID
where
(@CompanyID = -1 or @CompanyID = e.companies)
and
(@MainSelector is null or @MainSelector = e.wizard)
and
(@SecondarySelector is null or @SecondarySelector = e.name)
and
(@SysReference is null or @SysReference = e.SysReference)
and
(e.RecordDate >= @DateFrom and e.RecordDate <= @DateTo)
end
-- STEP 2: Flatten the required XML structure to provide a base for the pivot, and include other fields we wish to output
select dense_rank() over(order by ID) as ID,
T.RecordDate, T.CompanyName, T.SysReference, T.EntryName, T.MainSelector,
F.N.value('(FieldNameNode/text())[1]', 'nvarchar(max)') as FieldName,
F.N.value('(FieldNameValue/text())[1]', 'nvarchar(max)') as FieldValue
into #TempData
from @FeedbackXml as T
cross apply T.XMLCol.nodes('/root/companies/') as I(N) -- Xpath to the desired node start point
cross apply I.N.nodes('company') as F(N) -- The actual node collection that forms the "field name" and "field value" data
-- STEP 3: Pivot the #TempData table creating a dynamic column structure based on the selected XML nodes in step 2
declare @SQL nvarchar(max)
declare @Col nvarchar(max)
select @Col =
(
select distinct ','+quotename(FieldName)
from #TempData
for xml path(''), type
).value('substring(text()[1], 2)', 'nvarchar(max)')
set @SQL = 'select CompanyName, SysReference, EntryName, MainSelector, RecordDate, '+@Col+'
from #TempData
pivot (max(FieldValue) for FieldName in ('+@Col+')) as P'
exec (@SQL)
drop table #TempData
END
再次,真的只是添加了这个答案,以从我的角度提供一个完整的画面,并可能对其他人有所帮助。
【讨论】:
以上是关于使用 SQL 将 XML 结构转置/展平到列的主要内容,如果未能解决你的问题,请参考以下文章