【问题标题】:Create SQL Server table from XML data in specific format从特定格式的 XML 数据创建 SQL Server 表
【发布时间】:2018-02-28 22:16:46
【问题描述】:

我有以 XML 格式存储的数据,它的结构更像是一种 HTML 格式,而不是真正的 XML。元素头包含列名,行元素包含这些列的数据。

<root>
<head>
    <value>DeviceID</value>
    <value>VolumeName</value>
    <value>SizeGB</value>
    <value>FreeSpaceGB</value>
    <value>FreeSpacePercent</value>
</head>
<row>
    <value>C:</value>
    <value>c$OS-COMPUTER01</value>
    <value>126</value>
    <value>43</value>
    <value>34</value>
</row>
<row>
    <value>D:</value>
    <value>D$DB-COMPUTER01</value>
    <value>127</value>
    <value>104</value>
    <value>82</value>
</row>
<row>
    <value>E:</value>
    <value>E$COMPUTER01</value>
    <value>127</value>
    <value>106</value>
    <value>84</value>
</row>
</root>

这里的问题是,这只是我拥有的收藏品之一。还有其他集合项,它们具有相同的格式,但可能列数不同,因此我不能将列名硬编码到 select 语句中。此外,某些值可以为空。 其他收藏品示例如下:

<data>
    <head>
        <value>FriendlyName</value>
        <value>DnsNameList</value>
        <value>NotAfter</value>
        <value>NotBefore</value>
        <value>Thumbprint</value>
        <value>Issuer</value>
    </head>
    <row>
        <value />
        <value />
        <value>08/23/2021 07:59:59</value>
        <value>08/23/2011 08:00:00</value>
        <value>E1A7D7E3BD6CA0C832182248EF7F092A72F9EB67</value>
        <value>CN=Hewlett-Packard Private Class 2 Certification Authority, O=Hewlett-Packard Company, C=US, OU=IT Infrastructure, O=hp.com</value>
    </row>
    <row>
        <value>Microsoft Root Certificate Authority</value>
        <value />
        <value>05/10/2021 07:28:13</value>
        <value>05/10/2001 07:19:22</value>
        <value>CDD4EEAE6000AC7F40C3802C171E30148030C072</value>
        <value>CN=Microsoft Root Certificate Authority, DC=microsoft, DC=com</value>
    </row>
    <row>
        <value>Thawte Timestamping CA</value>
        <value />
        <value>01/01/2021 07:59:59</value>
        <value>01/01/1997 08:00:00</value>
        <value>BE36A4562FB2EE05DBB3D32323ADF445084ED656</value>
        <value>CN=Thawte Timestamping CA, OU=Thawte Certification, O=Thawte, L=Durbanville, S=Western Cape, C=ZA</value>
    </row>
    <row>
        <value>Microsoft Root Authority</value>
        <value />
        <value>12/31/2020 15:00:00</value>
        <value>01/10/1997 15:00:00</value>
        <value>A43489159A520F0D93D032CCAF37E7FE20A8B419</value>
        <value>CN=Microsoft Root Authority, OU=Microsoft Corporation, OU=Copyright (c) 1997 Microsoft Corp.</value>
    </row>
</data>

我需要能够将具有这种结构的数据解析到 SQL 表中,以便将来能够使用它。预期的结果是 SQL 表,看起来像这样:

DeviceID VolumeName      SizeGB FreeSpaceGB FreeSpacePercent
-------- --------------- ------ ----------- ----------------
C:       c$OS-COMPUTER01 126    43          34
D:       D$DB-COMPUTER01 127    104         82
E:       E$COMPUTER01    127    106         84

目前我将头数据和行数据解析到临时表中,并遍历各个表构建动态 SQL 代码,然后执行它以创建另一个表并将解析后的数据插入其中。

--code snippet which parses the rows
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 1)) n, xx.value('.', 'VARCHAR(MAX)') v INTO #rows
FROM (values(@x)) t1(x)
CROSS APPLY x.nodes('//row/value') t2(xx)

...

--code snippet building the INSERT queries
WHILE (@loopcounter * @columns) < @rows
BEGIN
    DECLARE @insertstatement VARCHAR(MAX)
    SET @insertstatement = 'INSERT INTO #result VALUES ('
    DECLARE @forcounter INT = 1
    WHILE @forcounter <= @columns
    BEGIN
        DECLARE @rownumber int = (@loopcounter * @columns) + @forcounter
        SELECT @insertstatement += '''' + REPLACE(v, '''', '''''') + ''', ' FROM #rows WHERE n = @rownumber

        SET @forcounter += 1
    END     
    SET @insertstatement = SUBSTRING(@insertstatement, 1, LEN(@insertstatement) - 1)
    SET @insertstatement += ')'
    EXEC (@insertstatement)
    SET @loopcounter += 1   
END

...

我搜索了这个论坛,但没有找到解决方案,该解决方案可以动态处理以这种方式存储的列名。您能告诉我可以做什么吗?

【问题讨论】:

    标签: xml tsql sql-server-2012 xml-parsing xquery


    【解决方案1】:

    以下查询将以非透视格式检索所有数据:

    DECLARE @xml XML=
    '<root>
    <head>
        <value>DeviceID</value>
        <value>VolumeName</value>
        <value>SizeGB</value>
        <value>FreeSpaceGB</value>
        <value>FreeSpacePercent</value>
    </head>
    <row>
        <value>C:</value>
        <value>c$OS-COMPUTER01</value>
        <value>126</value>
        <value>43</value>
        <value>34</value>
    </row>
    <row>
        <value>D:</value>
        <value>D$DB-COMPUTER01</value>
        <value>127</value>
        <value>104</value>
        <value>82</value>
    </row>
    <row>
        <value>E:</value>
        <value>E$COMPUTER01</value>
        <value>127</value>
        <value>106</value>
        <value>84</value>
    </row>
    </root>';
    

    --查询

    WITH ColumnNames AS
    (
        SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS ColumnInx
              ,c.value(N'text()[1]','nvarchar(max)') AS ColumnName
        FROM @xml.nodes(N'/root/head/value') AS A(c) 
    )
    ,TheRows AS
    (
        SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS RowInx
              ,r.query(N'value') AS TheValues
        FROM @xml.nodes(N'/root/row') AS B(r)
    )
    SELECT r.RowInx
          ,cn.ColumnInx
          ,cn.ColumnName
          ,r.TheValues.value(N'value[sql:column("ColumnInx")][1]','nvarchar(max)') AS row_value
    FROM TheRows AS r
    CROSS JOIN ColumnNames AS cn;
    

    结果

    +--------+-----------+------------------+-----------------+
    | RowInx | ColumnInx | ColumnName       | row_value       |
    +--------+-----------+------------------+-----------------+
    | 1      | 1         | DeviceID         | C:              |
    +--------+-----------+------------------+-----------------+
    | 1      | 2         | VolumeName       | c$OS-COMPUTER01 |
    +--------+-----------+------------------+-----------------+
    | 1      | 3         | SizeGB           | 126             |
    +--------+-----------+------------------+-----------------+
    | 1      | 4         | FreeSpaceGB      | 43              |
    +--------+-----------+------------------+-----------------+
    | 1      | 5         | FreeSpacePercent | 34              |
    +--------+-----------+------------------+-----------------+
    | 2      | 1         | DeviceID         | D:              |
    +--------+-----------+------------------+-----------------+
    | 2      | 2         | VolumeName       | D$DB-COMPUTER01 |
    +--------+-----------+------------------+-----------------+
    | 2      | 3         | SizeGB           | 127             |
    +--------+-----------+------------------+-----------------+
    | 2      | 4         | FreeSpaceGB      | 104             |
    +--------+-----------+------------------+-----------------+
    | 2      | 5         | FreeSpacePercent | 82              |
    +--------+-----------+------------------+-----------------+
    | 3      | 1         | DeviceID         | E:              |
    +--------+-----------+------------------+-----------------+
    | 3      | 2         | VolumeName       | E$COMPUTER01    |
    +--------+-----------+------------------+-----------------+
    | 3      | 3         | SizeGB           | 127             |
    +--------+-----------+------------------+-----------------+
    | 3      | 4         | FreeSpaceGB      | 106             |
    +--------+-----------+------------------+-----------------+
    | 3      | 5         | FreeSpacePercent | 84              |
    +--------+-----------+------------------+-----------------+
    

    如果您需要您的表格格式,这是通过动态创建的PIVOT 命令完成的。 Here is one example 怎么做。

    【讨论】:

    • 嗨,Shnugo,非常感谢!当我在更复杂的集合项目上尝试这个 - 包含证书数据时,它返回空表。示例记录: FriendlyName DnsNameList NotAfter NotBefore Thumbprint Issuer Microsoft Root Certificate Authority 2010 06/24/2035 06:04:01 06/24/2010 05:57:24 3B1EFD3A66EA28B16697394703A72CA340A05BD5 CN=Microsoft Root Certificate Authority 2010, O=Microsoft Corporation, L=雷德蒙德,S=华盛顿,C=US
    • @Mum,请使用编辑选项在问题中填写其他信息。然后发表评论让我保持警惕。请避免变色龙问题。如果这是一个新问题,请关闭它并开始一个新问题。
    • 感谢您的指导。我已经使用附加数据示例更新了原始问题,您的查询在该示例上返回空表。我还添加了简短的 sn-ps 代码,演示了我当前如何解析具有所需功能的数据,但代码有点丑陋,根本不像 SQL。此外,我想作为函数的结果返回这种动态创建的表 - 有没有办法在 MS SQL 中实现?
    • 好吧,愚蠢的我——在数据处理过程中,根元素的名称发生了变化。这就是空结果的原因。很抱歉,感谢您的帮助!
    • @Mum 您可以将/root/head 更改为//head/*/head。第一个将找到一个节点&lt;head&gt;,无论它在哪里,第二个将在一个根节点下找到这个节点,但是它被调用了。您需要进一步的帮助吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-05
    相关资源
    最近更新 更多