SQL Server:将 XML 数据转换为表
Posted
技术标签:
【中文标题】SQL Server:将 XML 数据转换为表【英文标题】:SQL Server : convert XML data onto table 【发布时间】:2016-02-03 18:26:11 【问题描述】:我需要帮助来微调我编写的这段代码。我是 SQL Server 的新手,我相信有更好的方法可以做到这一点,或者也许可以简化或微调以下代码以提高性能或节省内存资源。
基本上,我有这个 XML 数据:
<table_result id="001" subj_cd="cdaaa" grade="b" name="Phua Chu Kang"/>
我想根据 XML 数据创建一个如下所示的表
请注意以下几点:
SplitThis
不是内置函数(检查下面的代码)。
数据可以有空格,但由"
分隔。另请注意,对于该特定给定表,XML 数据可以具有不同数量的字段-数据对 - 在以下代码中称为 #dummy。即上面的示例 XML 数据有 4 个字段(id、subj_cd、grade、name),下一个 XML 数据可能有 5 个字段(即 id、name、职业、phone_no、地址)。在以下代码中,创建了#table_result 以匹配示例 XML 数据以便于演示。换句话说,表结构是已知的。所以我可以忽略 XML 数据中的字段名称,而专注于提取数据本身。
该代码在 SQL Server 2012 上运行良好(您可以直接复制粘贴运行代码),我能够如上所示。如果可能的话,我只需要微调它。我有这样的行: - - test blabla。您可以取消注释并尝试。我可以使用增强功能,例如避免使用临时表的数量或以任何方式替换代码中 row_number()
的使用。
/* remove all temp tables */
declare @sql varchar(5000)
SELECT @sql = isnull(@sql+';', '') + 'drop table ' + SUBSTRING(t.name, 1, CHARINDEX('___', t.name)-1)
FROM tempdb..sysobjects AS t
WHERE t.name LIKE '#%[_][_][_]%'
AND t.id =OBJECT_ID('tempdb..' + SUBSTRING(t.name, 1, CHARINDEX('___', t.name)-1));
exec (@sql)
/* end */
/* function */
drop function splitthis
go
create function splitthis(@separator char(1), @list varchar(max))
returns @returntable table(item nvarchar(max))
as
begin
declare @index int
declare @newtext varchar(max)
if @list = null
return
set @index = charindex(@separator, @list)
while not(@index = 0)
begin
set @newtext = rtrim(ltrim(left(@list, @index - 1)))
set @list = right(@list, len(@list) - @index)
insert into @returntable(item) values(@newtext)
set @index = charindex(@separator, @list)
end
insert into @returntable(item) values(rtrim(ltrim(@list)))
update @returntable set item='' where item is null
return
end
go
/* end of function */
/* create dummy tables */
create table #table_result
(id nvarchar(max), subj_cd nvarchar(max), grade nvarchar(max), name nvarchar(max))
create table #dummy (name nvarchar(max), data nvarchar(max))
insert into #dummy
values ('a', '<table_result id="001" subj_cd="cdaaa" grade="b" name="phua chu kang"/>');
--test : select * from #dummy
/* remove the fist non-data opening tag */
declare @record nvarchar(max)
select @record = data from #dummy where name = 'a'
select *, null as temp into #tempb from splitthis(' ',@record)
select *, row_number() over (order by temp) count into #tempc from #tempb
select item into #tempd from #tempc where #tempc.count>1
-- test : select * from #tempd
/* get the actual field & data into a single column table */
declare @temp varchar(max)
set @temp=''select @temp=@temp+' ' + item from #tempd
select *, null as temp into #tempe from splitthis('"',@temp)
select *, row_number() over (order by temp) count into #tempf from #tempe
select item, count into #tempg from #tempf
--test : select * from #tempg
/* prepare the data table */
select
case when #tempg.count % 2 = 0
then item
else null
end as data
into #temph
from #tempg
select data, null as temp into #tempi from #temph
select data, row_number() over (order by temp) count into #data from #tempi
where data is not null
--test : select * from #data
/* prepare the field table. */
select name, null as temp into #tempj
from tempdb.sys.columns where object_id=object_id('tempdb..#table_result');
select *, row_number() over (order by temp) count into #field from #tempj
--test : select * from #field
/* get the final table */
select a.name as field, b.data from #field a
left join #data b on a.count=b.count
【问题讨论】:
您发布的拆分器绝对是所有可用拆分器中性能最差的,遗憾的是它也是最常见的。有关性能要好得多的几种替代方案,请参阅本文。 sqlperformance.com/2012/07/t-sql-queries/split-strings 我想这可能会对你有所帮助Convert Xml to Table SQL Server 我想,这是XY-Problem。您不需要拆分代码的帮助,但实际上需要另一种方法。如果我没有弄错您的问题,那么问题是:我如何才能将属性及其值作为一般 XML-Element 的名称-值对来获取? 是的,另一种方法也很棒。下面发布的答案很好 【参考方案1】:这 - 使用 XML 方法 - 要容易得多!
试试这个:
DECLARE @xml XML='<table_result id="001" subj_cd="cdaaa" grade="b" name="Phua Chu Kang"/>';
SELECT One.Attr.value('fn:local-name(.)','varchar(max)') AS field
,One.Attr.value('.','varchar(max)') AS data
FROM @xml.nodes('table_result/@*') AS One(Attr)
结果
field data
id 001
subj_cd cdaaa
grade b
name Phua Chu Kang
现在我尝试模仿您的表结构(我建议从一开始就将数据存储为 XML!在这种情况下,您可以省略第一个 CROSS APPLY
和 CAST ... AS XML
):
DECLARE @tbl TABLE(name VARCHAR(10),data VARCHAR(MAX));
INSERT INTO @tbl VALUES
('a','<table_result id="001" subj_cd="cdaaa" grade="b" name="Phua Chu Kang"/>')
,('b','<Another test="test data" test2="test2 data"/>')
,('c','<OneMore x="x data" y="y data" z="z data"/>');
SELECT tbl.name
,One.Attr.value('fn:local-name(..)','varchar(max)') AS element
,One.Attr.value('fn:local-name(.)','varchar(max)') AS field
,One.Attr.value('.','varchar(max)') AS data
FROM @tbl AS tbl
CROSS APPLY(SELECT CAST(tbl.data AS XML)) AS MyData(AsXml)
CROSS APPLY MyData.AsXml.nodes('*/@*') AS One(Attr)
结果
name element field data
a table_result id 001
a table_result subj_cd cdaaa
a table_result grade b
a table_result name Phua Chu Kang
b Another test test data
b Another test2 test2 data
c OneMore x x data
c OneMore y y data
c OneMore z z data
【讨论】:
这是一个不错的方法!但是,我们如何使用带变量的 XML 节点函数呢? @Cache,第一个示例使用声明的 XML 变量显示了这种方法,第二个示例使用存储在表中的数据......无论如何,变量/列必须是“真实”XML,并且不仅仅是包含 XML 的 VARCHAR。最后一个很容易投... 我明白了。感谢您的信息!【参考方案2】:现在,我对 T-SQL XML 不是很擅长,但你不能这样:
create table #dummy (name nvarchar(max), data xml);
insert into #dummy
values ('a', '<table_result id="001" subj_cd="cdaaa" grade="b" name="phua chu kang"/>');
select 'id' "field",
elem.value('@id', 'nvarchar(50)') "data"
from #dummy
cross apply data.nodes('/table_result') tbl(elem)
union all
select 'subj_cd' "field",
elem.value('@subj_cd', 'nvarchar(50)') "data"
from #dummy
cross apply data.nodes('/table_result') tbl(elem)
union all
select 'grade' "field",
elem.value('@grade', 'nvarchar(50)') "data"
from #dummy
cross apply data.nodes('/table_result') tbl(elem)
union all
select 'name' "field",
elem.value('@name', 'nvarchar(50)') "data"
from #dummy
cross apply data.nodes('/table_result') tbl(elem);
请注意,我将 #dummy.data
的数据类型更改为 xml
。这是能够使用 XML 函数所必需的。
【讨论】:
OP 声明,需要一种通用方法(改变属性的数量和名称)......正如您在我的回答中看到的那样,这很容易实现...... @Shnugo 正如我所说,我不太擅长 T-SQL XML!如果您知道如何从 table 而不是变量而不使用上面的CROSS APPLY
方法获得相同的结果,我也希望看到这一点。
看看我的答案,我编辑了它并添加了一个示例如何对表数据执行相同操作...
还有一点:您在评论中声明without using CROSS APPLY
。为什么?访问根元素的属性存在一般限制。 CROSS APPLY
和.nodes()
允许您获得对节点的相对引用并允许您读取这些属性。在那里,在 XPath 中,您可以使用 *
而不是允许通用查询的实际名称。
@Shnugo 因为我很好奇这是唯一的方法还是最好的方法。我对CROSS APPLY
没有任何反对意见。 *** 上 90% 的 XML 答案使用保存在变量中的 XML,除非您编写一个存储过程来一次切碎 XML 一个片段,否则这基本上是无用的,因为您不能将它与基于集合的逻辑一起使用。我也没有发现 MSDN 上的 XML 文档很容易阅读,所以我很难用它来了解我可以做什么,更不用说我应该做什么。 以上是关于SQL Server:将 XML 数据转换为表的主要内容,如果未能解决你的问题,请参考以下文章