没有记录元素的XML需要使用TSQL / SQL Server转换为表

时间:2019-02-21 16:57:23

标签: sql sql-server xml tsql

没有记录元素的XML需要在SQL Server中使用T-SQL转换为表。

DECLARE @X XML = '<ROOT>
                      <RECORD>
                          <ID>1</ID>
                          <AppCode>Code 1</AppCode>
                          <ID>2</ID>
                          <AppCode>Code 2</AppCode>
                          <ID>3</ID>
                          <AppCode>Code 3</AppCode>
                          <ID>4</ID>
                          <AppCode>Code 4</AppCode>
                          <ID>5</ID>
                          <AppCode>Code 5</AppCode>
                      </RECORD>
                  </ROOT>'

我尝试过这样

SELECT
    CASE WHEN items.item.value('local-name(.)', 'varchar(300)') = 'ID' 
            THEN items.item.value('text()[1]', 'varchar(300)') 
    END AS ID,
    CASE WHEN items.item.value('local-name(.)', 'varchar(300)') = 'AppCode' 
            THEN items.item.value('text()[1]', 'varchar(300)') 
    END AS AppCode
FROM 
    @X.nodes('/ROOT/record/*') AS items(item)

结果不正确-

ID       AppCode
-----------------
1        NULL
NULL     Code 1
2        NULL
NULL     Code 2

我需要这样的结果:

ID    Appcode
---------------
1     Code 1
2     Code 2
3     Code 3
4     Code 4
5     Code 5

3 个答案:

答案 0 :(得分:0)

假设这是您要使用的格式:

ID    Appcode
---------------
1     Code 1
2     Code 2

这就是我要做的:

DECLARE @X XML = '
<ROOT>
    <RECORD>
        <ID>1</ID>
        <AppCode>Code 1</AppCode>
        <ID>2</ID>
        <AppCode>Code 2</AppCode>
    </RECORD>
</ROOT>';


SELECT V.ID,AppCode
FROM @X.nodes('/ROOT/RECORD') AS X(R)
     CROSS APPLY (VALUES(X.R.value('(./ID/text())[1]','int'),X.R.value('(./AppCode/text())[1]','varchar(10)')),
                        (X.R.value('(./ID/text())[2]','int'),X.R.value('(./AppCode/text())[2]','varchar(10)'))) V(ID,AppCode)

但是,这不会扩展。因此,如果您的ID为3、4,就不会给您更多的行。

答案 1 :(得分:0)

如果您可以修改XML格式并按照以下说明使用查询来获取结果

DECLARE @X XML = '<ROOT>
                  <RECORD>
                      <ID>1</ID>
                      <AppCode>Code 1</AppCode>
                  </RECORD>
                  <RECORD>                      
                      <ID>2</ID>
                      <AppCode>Code 2</AppCode>
                  </RECORD>
                  <RECORD>
                      <ID>3</ID>
                      <AppCode>Code 3</AppCode>
                  </RECORD>
                  <RECORD>
                      <ID>4</ID>
                      <AppCode>Code 4</AppCode>
                  </RECORD>
                  <RECORD>
                      <ID>5</ID>
                      <AppCode>Code 5</AppCode>
                  </RECORD>
              </ROOT>'


DECLARE @doc int
EXEC sp_xml_preparedocument @doc OUTPUT, @X

SELECT ID, AppCode
FROM OPENXML(@doc,'/ROOT/RECORD',2)
WITH (
ID int,
AppCode varchar(10)
)

输出会喜欢

ID  AppCode
1   Code 1
2   Code 2
3   Code 3
4   Code 4
5   Code 5

答案 2 :(得分:0)

更新2:这将是最好的方法

尝试一下

WITH Numbers AS
(SELECT TOP(@X.value('count(//ID)','int')) ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS Nmbr FROM master..spt_values)   
SELECT Nmbr 
      ,@X.value('(//ID[sql:column("Nmbr")])[1]','int') AS ID
      ,@X.value('(//AppCode[sql:column("Nmbr")])[1]','nvarchar(max)') AS AppCode
      ,@X.value('(//SomeMore[sql:column("Nmbr")])[1]','nvarchar(max)') AS SomeMore
FROM Numbers;

想法

  • 计算ID并返回带有运行编号(在本例中为1,2,3,4,5)的派生。然后使用该数字按元素的位置进行寻址。

还有两种方法,只是为了好玩:-)

这应该可以很快地处理许多列:

DECLARE @X XML = '<ROOT>
                      <RECORD>
                          <ID>1</ID>
                          <AppCode>Code 1</AppCode>
                          <ID>2</ID>
                          <AppCode>Code 2</AppCode>
                          <ID>3</ID>
                          <AppCode>Code 3</AppCode>
                          <ID>4</ID>
                          <AppCode>Code 4</AppCode>
                          <ID>5</ID>
                          <AppCode>Code 5</AppCode>
                      </RECORD>
                  </ROOT>';
WITH allIDs AS
(
    SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS Pos
          ,id.value('text()[1]','int') AS ID
    FROM @X.nodes('/ROOT/RECORD/ID') A(id)
)
SELECT ai.Pos
      ,ai.ID
      ,@X.value('/ROOT[1]/RECORD[1]/AppCode[sql:column("ai.Pos")][1]','nvarchar(max)') AS AppCode
      ,@X.value('/ROOT[1]/RECORD[1]/SomeMore[sql:column("ai.Pos")][1]','nvarchar(max)') AS SomeMore
FROM allIDs ai
ORDER BY ai.Pos;

想法:

  • 阅读ID并找到其位置
  • 通过sql:column()使用ID的位置读取相应的元素

更新:使用条件聚合

的另一种方法

这应该更快:

WITH AllNodes AS
(
    SELECT (ROW_NUMBER() OVER(ORDER BY (SELECT NULL))-1) / 3 AS RowGroup
          ,nd.value('local-name(.)','nvarchar(max)') AS NodeName
          ,nd.value('text()[1]','nvarchar(max)') AS NodeValue
    FROM @X.nodes('/ROOT/RECORD/*') A(nd)
)
SELECT RowGroup
      ,MAX(CASE WHEN NodeName='ID' THEN NodeValue END) AS ID
      ,MAX(CASE WHEN NodeName='AppCode' THEN NodeValue END) AS AppCode
      ,MAX(CASE WHEN NodeName='SomeMore' THEN NodeValue END) AS SomeMore
FROm AllNodes
GROUP BY RowGroup

想法

  • 我们将所有节点读为EAV结构。 (ROW_NUMBER-1)/3(整数除法!)将返回一个我们可以在GROUP BY中使用的组索引。其余的是老式的PIVOT

两种方法的警告

  • 这需要一个不丢失元素的结构。如果XML可能包含缺少追随者节点之一的部分,则此方法将无效。
  • 使用ROW_NUMBER() OVER(ORDER BY (SELECT NULL))的解决方案在我使用过的所有情况下都对我有用。但是值得一提的是,不能保证返回正确的位置。