【问题标题】:XML Shredding Dynamic SQLXML 粉碎动态 SQL
【发布时间】:2020-03-02 09:42:10
【问题描述】:

使用the instructions from the 1st answer here,我正在尝试在 SQL Server 2012 表中分解一些 XML,如下所示:

表A

+------+--------+
|  ID  | ColXML |
+------+--------+
| 0001 | <xml1> |
| 0002 | <xml2> |
| ...  | ...    |
+------+--------+

xml1 看起来像这样:

<Attributes>
    <Attribute name="address1">301 Main St</Attribute>
    <Attribute name="city">Austin</Attribute>
</Attributes>

xml2 看起来像这样:

<Attributes>
    <Attribute name="address1">501 State St</Attribute>
    <Attribute name="address2">Suite 301</Attribute>
    <Attribute name="state">Texas</Attribute>
</Attributes>

在任何给定的行中都有不同数量的属性。

我正在尝试将其展平为如下所示的关系表:

+------+--------------+-----------+--------+-------+
|  ID  |   address1   | address2  |  city  | state |
+------+--------------+-----------+--------+-------+
| 0001 | 301 Main St  | NULL      | Austin | NULL  |
| 0002 | 501 State St | Suite 301 | NULL   | Texas |
+------+--------------+-----------+--------+-------+

这是我尝试过的代码,它在表#T 中返回 0 行:

select dense_rank() over(order by ID, I.N) as ID,
       F.N.value('(*:Name/text())[1]', 'varchar(max)') as Name,
       F.N.value('(*:Values/text())[1]', 'varchar(max)') as Value
into #T
from TableA as T
  cross apply T.Attributes.nodes('/ColXML') as I(N)
  cross apply I.N.nodes('ColXML') 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);

任何帮助将不胜感激。

【问题讨论】:

  • #T 是临时表吗?
  • 根据您的代码,您正在寻找示例 xml 中不存在的节点 ColXML
  • 感谢关于示例数据、自己的代码和预期输出的好问题(我这边 +1)。你可能会refer to this very related question。我建议将您的值读入 EAV 暂存表并从那里开始。否则,您将不得不切碎您的 XLM 两次,一次是创建动态语句,另一次是读取它。这会很慢...
  • 感谢您提供链接并推荐 EAV 暂存表,这绝对有帮助!

标签: sql sql-server xml xquery


【解决方案1】:

这里是一个 DDL 和 XQuery,用于分解表中的 XML。不需要任何动态 SQL。 MS SQL Server 支持 XQuery 1.0 标准的子集。 Microsoft 需要实施 XQuery 3.1 以使其数据库更加强大。

SQL

-- DDL and sample data population, start
DECLARE @tbl TABLE (ID VARCHAR(10) PRIMARY KEY, ColXML XML);
INSERT INTO @tbl (ID, ColXML)
VALUES
('0001', N'<Attributes>
    <Attribute name="address1">301 Main St</Attribute>
    <Attribute name="city">Austin</Attribute>
</Attributes>'),
('0002', N'<Attributes>
    <Attribute name="address1">501 State St</Attribute>
    <Attribute name="address2">Suite 301</Attribute>
    <Attribute name="state">Texas</Attribute>
</Attributes>');
-- DDL and sample data population, end

SELECT ID
    , col.value('(Attribute[@name="address1"]/text())[1]','VARCHAR(30)') AS [address1]
    , col.value('(Attribute[@name="address2"]/text())[1]','VARCHAR(30)') AS [address2]
    , col.value('(Attribute[@name="city"]/text())[1]','VARCHAR(30)') AS [city]
    , col.value('(Attribute[@name="state"]/text())[1]','VARCHAR(30)') AS [state]
FROM @tbl AS tbl
    CROSS APPLY tbl.ColXML.nodes('/Attributes') AS tab(col);

输出

+------+--------------+-----------+--------+-------+
|  ID  |   address1   | address2  |  city  | state |
+------+--------------+-----------+--------+-------+
| 0001 | 301 Main St  | NULL      | Austin | NULL  |
| 0002 | 501 State St | Suite 301 | NULL   | Texas |
+------+--------------+-----------+--------+-------+

【讨论】:

  • 非常感谢您的详尽回复!除了有 100 个属性/列之外,该代码可以工作,因此指定每个属性/列是不切实际的。
  • 很高兴听到建议的解决方案对您有用。请不要忘记标记为答案。恕我直言,它仍然比动态 SQL 更好。易于创建,易于维护和支持。
  • 好答案,我这边+1
【解决方案2】:

这是一个基于我之前的工作建议的动态 SQL 解决方案。它发出相同的输出。

SQL

USE tempdb;
GO

DROP TABLE IF EXISTS #tbl;

-- DDL and sample data population, start
CREATE TABLE #tbl (ID VARCHAR(10) PRIMARY KEY, ColXML XML);
INSERT INTO #tbl (ID, ColXML)
VALUES
('0001', N'<Attributes>
    <Attribute name="address1">301 Main St</Attribute>
    <Attribute name="city">Austin</Attribute>
</Attributes>'),
('0002', N'<Attributes>
    <Attribute name="address1">501 State St</Attribute>
    <Attribute name="address2">Suite 301</Attribute>
    <Attribute name="state">Texas</Attribute>
</Attributes>');
-- DDL and sample data population, end

DECLARE @CrLf CHAR(2) = CHAR(13) + CHAR(10)
    , @sql VARCHAR(MAX) = 'SELECT ID ';

SET @sql += @CrLf;

;WITH rs AS
(
    SELECT DISTINCT col.value('@name','VARCHAR(30)') AS colName
    FROM #tbl AS tbl
        CROSS APPLY tbl.ColXML.nodes('/Attributes/Attribute') AS tab(col)
)
SELECT @sql += ', col.value(''(Attribute[@name="' + colName + '"]/text())[1]'',''VARCHAR(30)'') AS [' + colName + ']' + @CrLf 
FROM rs;

SET @sql += 'FROM #tbl AS tbl
    CROSS APPLY tbl.ColXML.nodes(''/Attributes'') AS tab(col);';

PRINT @sql;
EXEC(@sql);

【讨论】:

  • 好答案,我这边 +1,虽然我会避免 古怪的更新...
  • 这个工作完美,虽然另一个答案也很有用。非常感谢您的帮助,我真的很感激!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多