【问题标题】:T-SQL Parse XML Response in table formatT-SQL 以表格格式解析 XML 响应
【发布时间】:2020-03-21 05:39:13
【问题描述】:

我正在努力解析我拥有的 XML 响应。我需要标题值是列,记录值是它们各自行内的数据。以下是带有标题值和 1 条记录的返回示例。

如果记录显示 xsi:nil="true" 将为 NULL

<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
   <env:Header/>
   <env:Body>
      <ns2:getReportResultResponse xmlns:ns2="http://service.apiendpoint.com">
         <return>
            <header>
               <values>
                  <data>CUSTOMER NAME</data>
                  <data>DISPOSITION GROUP A</data>
                  <data>DISPOSITION GROUP B</data>
                  <data>DISPOSITION GROUP C</data>
                  <data>DISPOSITION PATH</data>
                  <data>FIRST DISPOSITION</data>
                  <data>LAST DISPOSITION</data>
                  <data>LIST NAME</data>
               </values>
            </header>
            <records>
               <values>
                  <data>Mark Smith</data>
                  <data>12</data>
                  <data>19</data>
                  <data>23</data>
                  <data xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
                   <data xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
                   <data xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
                  <data>Tier 1</data>
               </values>
            </records>
            </return>
      </ns2:getReportResultResponse>
   </env:Body>
</env:Envelope>

【问题讨论】:

  • 您的 xml 不包含列的数据类型信息。是否有任何其他来源可以推断数据类型?
  • 这正是 API 向我提供响应的方式。我将使用插入的数据类型设置目标表,但我知道数据类型的唯一方法是通过我通过 API 运行的报告。这个响应是报告结果,因为它们来了
  • 那么,将xml转换为sql表的目的是什么?有计划查询SELECT SUM([DISPOSITION GROUP A] .. GROUP BY [CUSTOMER NAME] 的表吗?

标签: sql sql-server xml


【解决方案1】:
declare @x xml = N'
<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
   <env:Header/>
   <env:Body>
      <ns2:getReportResultResponse xmlns:ns2="http://service.apiendpoint.com">
         <return>
            <header>
               <values>
                  <data>CUSTOMER NAME</data>
                  <data>DISPOSITION GROUP A</data>
                  <data>DISPOSITION GROUP B</data>
                  <data>DISPOSITION GROUP C</data>
                  <data>DISPOSITION PATH</data>
                  <data>FIRST DISPOSITION</data>
                  <data>LAST DISPOSITION</data>
                  <data>LIST NAME</data>
               </values>
            </header>
            <records>
               <values>
                  <data>Mark Smith</data>
                  <data>12</data>
                  <data>19</data>
                  <data>23</data>
                  <data xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
                   <data xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
                   <data xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
                  <data>Tier 1</data>
               </values>
               <values>
                  <data>B</data>
                  <data>2</data>
                  <data>22</data>
                  <data>222</data>
                  <data xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
                   <data xsi:nil="false" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/><!-- ?? -->
                   <data xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
                   <data>Tier 2</data>
               </values>               
            </records>
            </return>
      </ns2:getReportResultResponse>
   </env:Body>
</env:Envelope>
';

select @x;

declare @sql nvarchar(max) = N'';

with xmlnamespaces ('http://schemas.xmlsoap.org/soap/envelope/' as env, 'http://service.apiendpoint.com' as ns2)
select 
    @sql = @sql + ',r.rec.value(''data[' + cast(colid as nvarchar(10)) + '][not(@xsi:nil="true")]'', ''nvarchar(500)'') as ' + colname
from 
(
    select 
        quotename(hd.h.value('.', 'sysname')) as colname,
        row_number() over(order by hd.h) as colid
    from @x.nodes('/env:Envelope/env:Body/ns2:getReportResultResponse/return/header/values/data') as hd(h)
) as src
order by colid;

select @sql = stuff(@sql, 1, 1, N'');

select @sql = N'with xmlnamespaces (''http://schemas.xmlsoap.org/soap/envelope/'' as env, ''http://service.apiendpoint.com'' as ns2, ''http://www.w3.org/2001/XMLSchema-instance'' as xsi)
select 
' + @sql + N'
from @x.nodes(''/env:Envelope/env:Body/ns2:getReportResultResponse/return/records/values'') as r(rec)
';

exec sp_executesql @stmt = @sql, @params = N'@x xml', @x = @x;

【讨论】:

  • 好答案,我这边+1!
【解决方案2】:

假设您的 XML 数据位于 SQL Server 变量 @XmlData 中,您可以使用此 XQuery 来获取列名(“标题”):

WITH XMLNAMESPACES ('http://schemas.xmlsoap.org/soap/envelope/' AS env, 'http://service.apiendpoint.com' AS ns2)
    SELECT
        XCol.value('(.)[1]', 'varchar(50)')
    FROM
        @XmlData.nodes('/env:Envelope/env:Body/ns2:getReportResultResponse/return/header/values/data') AS XHdr(XCol);

这相当简单,因为您可以假设每个标头实际上都是一个字符串(因此您可以进行 .value('(.)[1]', 'varchar(50)') 调用并确保安全)。

但是,对于数据——正如@Serg 在评论中已经提到的——除非你能以某种方式知道(或找出)数据元素的数据类型是什么,否则这将变得更加棘手。 .. 使用相同的方法 - 假设所有内容都是字符串 - 会起作用 - 但是您可能会丢失有关数据位的有价值信息:

WITH XMLNAMESPACES ('http://schemas.xmlsoap.org/soap/envelope/' AS env, 'http://service.apiendpoint.com' AS ns2)
    SELECT
        XCol.value('(.)[1]', 'varchar(50)')
    FROM
        @XmlData.nodes('/env:Envelope/env:Body/ns2:getReportResultResponse/return/records/values/data') AS XData(XCol)

【讨论】:

    【解决方案3】:

    这是另一种解决方案。非常接近@lptr的方法。

    它使用XQueryFLWOR表达式来构造最终SQL语句的动态SELECT子句。

    SQL

    DECLARE @x xml = N'
    <env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
       <env:Header/>
       <env:Body>
          <ns2:getReportResultResponse xmlns:ns2="http://service.apiendpoint.com">
             <return>
                <header>
                   <values>
                      <data>CUSTOMER NAME</data>
                      <data>DISPOSITION GROUP A</data>
                      <data>DISPOSITION GROUP B</data>
                      <data>DISPOSITION GROUP C</data>
                      <data>DISPOSITION PATH</data>
                      <data>FIRST DISPOSITION</data>
                      <data>LAST DISPOSITION</data>
                      <data>LIST NAME</data>
                   </values>
                </header>
                <records>
                   <values>
                      <data>Mark Smith</data>
                      <data>12</data>
                      <data>19</data>
                      <data>23</data>
                      <data xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
                       <data xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
                       <data xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
                      <data>Tier 1</data>
                   </values>
                   <values>
                      <data>B</data>
                      <data>2</data>
                      <data>22</data>
                      <data>222</data>
                      <data xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
                       <data xsi:nil="false" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/><!-- ?? -->
                       <data xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
                       <data>Tier 2</data>
                   </values>               
                </records>
                </return>
          </ns2:getReportResultResponse>
       </env:Body>
    </env:Envelope>';
    
    DECLARE @sql NVARCHAR(MAX) = N''
        , @separator CHAR(1) = ',';
    
    WITH XMLNAMESPACES ('http://schemas.xmlsoap.org/soap/envelope/' as env, 'http://service.apiendpoint.com' as ns2)
    SELECT @sql = @x.query('
        for $r in /env:Envelope/env:Body/ns2:getReportResultResponse/return/header/values/data
        let $pos := count(env:Envelope/env:Body/ns2:getReportResultResponse/return/header/values/data[. << $r]) + 1
        let $line := concat("c.value(''(data[", string($pos), "]/text())[1]'', ''VARCHAR(50)'') AS [", string(($r/text())[1]),"]")
        return if ($r is (/env:Envelope/env:Body/ns2:getReportResultResponse/return/header/values/data[last()])[1]) then string($line)
                else concat($line, sql:variable("@separator"))
    ').value('.', 'NVARCHAR(MAX)');
    
    SET @sql = N';WITH XMLNAMESPACES (''http://schemas.xmlsoap.org/soap/envelope/'' as env, ''http://service.apiendpoint.com'' as ns2)
    SELECT ' + @sql + N'
    FROM @x.nodes(''/env:Envelope/env:Body/ns2:getReportResultResponse/return/records/values'') AS t(c)
    ';
    
    EXEC sp_executesql @stmt = @sql, @params = N'@x xml', @x = @x;
    

    输出

    +---------------+---------------------+---------------------+---------------------+------------------+-------------------+------------------+-----------+
    | CUSTOMER NAME | DISPOSITION GROUP A | DISPOSITION GROUP B | DISPOSITION GROUP C | DISPOSITION PATH | FIRST DISPOSITION | LAST DISPOSITION | LIST NAME |
    +---------------+---------------------+---------------------+---------------------+------------------+-------------------+------------------+-----------+
    | Mark Smith    |                  12 |                  19 |                  23 | NULL             | NULL              | NULL             | Tier 1    |
    | B             |                   2 |                  22 |                 222 | NULL             | NULL              | NULL             | Tier 2    |
    +---------------+---------------------+---------------------+---------------------+------------------+-------------------+------------------+-----------+
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多