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 APPLYCAST ... 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 数据转换为表的主要内容,如果未能解决你的问题,请参考以下文章

SQL Server 2012将几个逗号分隔的值转换为表行/列

SQL Server - 将标量函数转换为表值函数

SQL Server 中的 JSON 数据

将 SQL 代码(作为字符串)转换为表 - 动态数据透视

将字符串转换为xml并插入Sql Server

将 XML 记录转换为表