SQL XML提取变量属性名称和值

SQL XML提取变量属性名称和值,sql,xml,Sql,Xml,即使在读了一天stack overflow之后,我也无法完全理解这一点 我有一个带有XML列的SQL表。根标记中可以有变量属性名和最多5个数量,我希望将名称和数据与其他信息一起放入表中 我已经拼凑了一个解决方案,这个解决方案就足够了,但我真的不喜欢它,因为我使用row_number生成字段号,然后将其连接回来,所以我依靠SQL以正确的顺序提取属性名,我不确定这是否可靠?它当然没有感觉到 我应该如何以更稳健的方式进行这项工作 带有数据的示例代码 ;with xmldata as ( SEL

即使在读了一天stack overflow之后,我也无法完全理解这一点

我有一个带有XML列的SQL表。根标记中可以有变量属性名和最多5个数量,我希望将名称和数据与其他信息一起放入表中

我已经拼凑了一个解决方案,这个解决方案就足够了,但我真的不喜欢它,因为我使用row_number生成字段号,然后将其连接回来,所以我依靠SQL以正确的顺序提取属性名,我不确定这是否可靠?它当然没有感觉到

我应该如何以更稳健的方式进行这项工作

带有数据的示例代码

    ;with xmldata as (
SELECT 1 as id, 
CAST ('<CHG client="c;EN" work_order="c;130102">  <COL NAM="description" TYP="c">    <OLD>Electricity control on CONST01-CCU</OLD>    <NEW>Electricity control on CONST01-CCU93</NEW>
  </COL>  <COL NAM="last_update" TYP="d">    <OLD>2021-03-01 09:43:40</OLD>    <NEW>2021-03-01 09:43:40</NEW>  </COL></CHG>' AS XML) xml_data
  UNION ALL 
  SELECT 2 as ID,  
  CAST( '<CHG menu_id="c;40906" user_id="c;" role_id="c;SYSTEM"><COL NAM="bflag" TYP="i"><NEW>8</NEW></COL><COL NAM="last_update" TYP="d"><NEW>2021-03-01 11:52:40</NEW>
  </COL><COL NAM="menu_id" TYP="c"><NEW>40906</NEW></COL><COL NAM="role_id" TYP="c"><NEW>SYSTEM</NEW></COL><COL NAM="tree_type" TYP="i">
  <NEW>1</NEW></COL><COL NAM="user_id" TYP="c"><NEW></NEW></COL><COL NAM="user_stamp" TYP="c"><NEW>TEST1</NEW></COL> </CHG> ' AS XML) as xml_data) 

  ,
cols as (SELECT id,  attrs.z.value('local-name(.)[1]', 'nvarchar(max)') as attr,
ROW_NUMBER() OVER(PARTITION BY id ORDER BY id ) AS rowno
FROM xmldata d
CROSS APPLY d.xml_data.nodes('/CHG/@*') as attrs(z)
)


SELECT d.id,
cols1.attr key_1_name,
right( change.x.value('(@*)[1]','varchar(255)'), len(change.x.value('(@*)[1]','varchar(255)')) - charindex(';', change.x.value('(@*)[1]','varchar(255)'))) as key_1,
cols2.attr key_2_name,
right( change.x.value('(@*)[2]','varchar(255)'), len(change.x.value('(@*)[2]','varchar(255)')) - charindex(';', change.x.value('(@*)[2]','varchar(255)'))) as key_2,
cols3.attr key_3_name,
right( change.x.value('(@*)[3]','varchar(255)'), len(change.x.value('(@*)[3]','varchar(255)')) - charindex(';', change.x.value('(@*)[3]','varchar(255)'))) as key_3,
cols4.attr key_4_name,
right( change.x.value('(@*)[4]','varchar(255)'), len(change.x.value('(@*)[4]','varchar(255)')) - charindex(';', change.x.value('(@*)[4]','varchar(255)'))) as key_4,
cols5.attr key_5_name,
right( change.x.value('(@*)[5]','varchar(255)'), len(change.x.value('(@*)[5]','varchar(255)')) - charindex(';', change.x.value('(@*)[5]','varchar(255)'))) as key_5,

col.y.value('(@NAM)[1]','varchar(255)') as column_name,
col.y.value('(OLD)[1]','varchar(255)') as old_value,
col.y.value('(NEW)[1]','varchar(255)') as new_value
FROM xmldata d
OUTER APPLY d.xml_data.nodes('/CHG') as change(x)
OUTER APPLY d.xml_data.nodes('/CHG/COL') as col(y)
LEFT JOIN cols cols1 
ON d.id = cols1.id  AND cols1.rowno =1 
LEFT JOIN cols cols2
ON d.id = cols2.id  AND cols2.rowno =2
LEFT JOIN cols cols3
ON d.id = cols3.id  AND cols3.rowno =3
LEFT JOIN cols cols4
ON d.id = cols4.id  AND cols4.rowno =4
LEFT JOIN cols cols5
ON d.id = cols5.id  AND cols5.rowno =5

请尝试以下解决方案

层次化XML通过链式交叉应用子句和适当的.nodes方法调用本身非常好地支持隐含关系

所以切碎很容易

SQL 1


在提问时,您需要提供一个最小的可复制示例。请参考以下链接:请提供以下内容:1 DDL和示例数据填充,即创建表和插入T-SQL语句。2您需要做什么,即逻辑和代码尝试在T-SQL中实现它。3期望输出,基于上述1中的样本数据。4您的SQL Server版本选择@@version;嗯,数据集都是自包含的,是基于CTE的查询,所以我认为1是没有意义的,2和3我认为是在我的解释中给出的,加上运行代码可以获得期望的结果,我正在寻找一种更健壮的方法来实现解决方案。这个版本也很有意义,因为我相信CTE和XML数据类型从2005年就已经存在了?谢谢,我需要的语法是:change.x.value'local-name@*[1],'VARCHAR30'@AdamDavies,很高兴听到建议的解决方案适合您。请投票:
-- DDL and sample data population, start
DECLARE @tbl TABLE (id INT IDENTITY PRIMARY KEY, xml_data XML);
INSERT INTO @tbl (xml_data) VALUES
(N'<CHG client="c;EN" work_order="c;130102">
    <COL NAM="description" TYP="c">
        <OLD>Electricity control on CONST01-CCU</OLD>
        <NEW>Electricity control on CONST01-CCU93</NEW>
    </COL>
    <COL NAM="last_update" TYP="d">
        <OLD>2021-03-01 09:43:40</OLD>
        <NEW>2021-03-01 09:43:40</NEW>
    </COL>
</CHG>'),
(N'<CHG menu_id="c;40906" user_id="c;" role_id="c;SYSTEM">
    <COL NAM="bflag" TYP="i">
        <NEW>8</NEW>
    </COL>
    <COL NAM="last_update" TYP="d">
        <NEW>2021-03-01 11:52:40</NEW>
    </COL>
    <COL NAM="menu_id" TYP="c">
        <NEW>40906</NEW>
    </COL>
    <COL NAM="role_id" TYP="c">
        <NEW>SYSTEM</NEW>
    </COL>
    <COL NAM="tree_type" TYP="i">
        <NEW>1</NEW>
    </COL>
    <COL NAM="user_id" TYP="c">
        <NEW></NEW>
    </COL>
    <COL NAM="user_stamp" TYP="c">
        <NEW>TEST1</NEW>
    </COL>
</CHG>');
-- DDL and sample data population, end

DECLARE @prefix CHAR(2) = 'c;'
, @tilde CHAR(1) = '~'
, @separator CHAR(1) = ';';

SELECT id
    , a.value('local-name((@*)[1])', 'VARCHAR(30)') AS key1
    , REPLACE(a.value('(@*)[1]', 'VARCHAR(30)'), @prefix, '') AS value1
    , a.value('local-name((@*)[2])', 'VARCHAR(30)') AS key2
    , REPLACE(a.value('(@*)[2]', 'VARCHAR(30)'), @prefix, '') AS value2
    , a.value('local-name((@*)[3])', 'VARCHAR(30)') AS key3
    , REPLACE(a.value('(@*)[3]', 'VARCHAR(30)'), @prefix, '') AS value3
    , a.value('local-name((@*)[4])', 'VARCHAR(30)') AS key4
    , REPLACE(a.value('(@*)[4]', 'VARCHAR(30)'), @prefix, '') AS value4
    , a.value('local-name((@*)[5])', 'VARCHAR(30)') AS key5
    , REPLACE(a.value('(@*)[5]', 'VARCHAR(30)'), @prefix, '') AS value5
    , y.value('@NAM', 'varchar(255)') AS column_name
    , y.value('(OLD/text())[1]', 'varchar(255)') AS old_value
    , y.value('(NEW/text())[1]', 'varchar(255)') AS new_value
FROM @tbl
    CROSS APPLY xml_data.nodes('/CHG') AS t1(a)
    CROSS APPLY t1.a.nodes('COL') AS t2(y);
SELECT id
    , a.value('local-name((@*)[1])', 'VARCHAR(30)') AS key1
    , REPLACE(a.value('(@*)[1]', 'VARCHAR(30)'), @prefix, '') AS value1
    , (
      SELECT LEFT(s.value, LEN(s.value) - 1)
      FROM STRING_SPLIT(a.value('(@*)[1]', 'VARCHAR(30)') + @tilde, @separator) AS s
      WHERE s.value LIKE '%' + @tilde
      ) AS value1
    , a.value('local-name((@*)[2])', 'VARCHAR(30)') AS key2
    , REPLACE(a.value('(@*)[2]', 'VARCHAR(30)'), @prefix, '') AS value2
    , a.value('local-name((@*)[3])', 'VARCHAR(30)') AS key3
    , REPLACE(a.value('(@*)[3]', 'VARCHAR(30)'), @prefix, '') AS value3
    , a.value('local-name((@*)[4])', 'VARCHAR(30)') AS key4
    , REPLACE(a.value('(@*)[4]', 'VARCHAR(30)'), @prefix, '') AS value4
    , a.value('local-name((@*)[5])', 'VARCHAR(30)') AS key5
    , REPLACE(a.value('(@*)[5]', 'VARCHAR(30)'), @prefix, '') AS value5
    , y.value('@NAM', 'varchar(255)') AS column_name
    , y.value('(OLD/text())[1]', 'varchar(255)') AS old_value
    , y.value('(NEW/text())[1]', 'varchar(255)') AS new_value
FROM @tbl
    CROSS APPLY xml_data.nodes('/CHG') AS t1(a)
    CROSS APPLY t1.a.nodes('COL') AS t2(y);