从“select for xml raw”获取数据

时间:2016-09-05 08:29:04

标签: sql sql-server xml

我有一个select for xml raw statment的查询。结果看起来像

<row link="someLink" DATASOURCE="SomeSystem" obj_id="1" err_id="1" />
<row link="someLink" DATASOURCE="SomeSystem" obj_id="1" err_id="2" />

<row UWI="123" Xcord="2.4" obj_id="1" err_id="1" /> 
<row UWI="122" Xcord="1.4"obj_id="1" err_id="2" />

并且所有xml都存储在一个单元格中。每个单元格可以有不同的属性名称和数量。

我想从xml中选择它回到像

这样的表中
link____|DATASOURCE  |obj_id|err_id   
someLink|SomeSystem__|1____ |1

someLink|SomeSystem__|1____ |2

但节点属性是动态的。

我可以使用

获取id和值列表
select 
   t.c.value('local-name(.)', 'nvarchar(128)') as [id],
   t.c.value('.', 'nvarchar(128)') as [value]
from @XMLVal.nodes('row/@*') as t(c)

更新

我的解决方案

declare 
@colNames TABLE(
  id INT IDENTITY,
  colName nvarchar(max)
);
declare @XMLVal XML;
set @XMLVal = '<row link="someLink" DATASOURCE="SomeSystem" obj_id="1" err_id="1" />
<row link="someLink" DATASOURCE="SomeSystem" obj_id="1" err_id="2" />';
-----DEFINE COLUMN NAMES-------------
insert into @colNames
select distinct
       t.c.value('local-name(.)', 'nvarchar(128)') as [ID]
from @XMLVal.nodes('row/@*') as t(c) 
-----COUNT COLUMNS QNTY---------------------
DECLARE @MaxCount INT;
SELECT @MaxCount = count(*) from @colNames
-----GENERATE SQL---------------------------
DECLARE @SQL NVARCHAR(max), @i INT, @curentColumnName nvarchar(max);
SET @i = 0;
SET @SQL = 'declare @XMLVal XML;
SET @XMLVal = ''<row link="someLink" DATASOURCE="SomeSystem" obj_id="1" err_id="1" />
<row link="someLink" DATASOURCE="SomeSystem" obj_id="1" err_id="2" />'';
select ';
WHILE @i < @MaxCount
BEGIN
   SET @i = @i + 1;
     select @curentColumnName = colName from @colNames where id = @i;
     SET @SQL = @SQL + ' t.c.value(''@' + @curentColumnName + ''', ''nvarchar(128)'') as ' + @curentColumnName;
    if (@i < @MaxCount ) begin
       SET @SQL = @SQL + ', ';
    end 
 END
 SET @SQL = @SQL + ' from @XMLVal.nodes(''/row'') as t(c)'; 
 EXEC sp_executesql @SQL; 

1 个答案:

答案 0 :(得分:0)

在您详细了解目标的情况下,使用XPath直接解决XML的元素和属性会更容易。

对于未知XML的通用读取,您的方法是完全正确的 - 但这里很复杂。您必须先按行逐行读取属性,然后必须执行某种PIVOTGROUP BY with Max(CASE... )才能将它们作为普通表格返回。

请注意,您的XML似乎没有root元素。这在SQL查询中是可能的,但是 - 严格看 - 无效......

DECLARE @XMLVal XML=
'<row link="someLink" DATASOURCE="SomeSystem" obj_id="1" err_id="1" />
<row link="someLink" DATASOURCE="SomeSystem" obj_id="1" err_id="2" />';

select 
   t.c.value('@link', 'nvarchar(128)') as link,
   t.c.value('@DATASOURCE', 'nvarchar(128)') as DATASOURCE,
   t.c.value('@obj_id', 'int') as obj_id,
   t.c.value('@err_id', 'int') as err_id
from @XMLVal.nodes('/row') as t(c)

更新

正如您所阐述的那样,XML的形状完全不同。要达到输出目标,您必须动态创建列名。如果这是动态的,但绑定到一个封闭的集合,我建议某种PIVOT。但是 - 在你的情况下 - 这似乎是完全免费的。这必须使用动态SQL完成。这意味着,您将语句创建为字符串(完全按照您需要键入它以获得正确的结果。比使用EXEC执行此命令。

一个回退是,你不能在 ad-hoc SQL中使用它。

尝试此操作并将变量@RowID设置为1或2:

CREATE TABLE #tbl(ID INT IDENTITY,XMLVal XML);
INSERT INTO #tbl VALUES
 ('<row link="someLink" DATASOURCE="SomeSystem" obj_id="1" err_id="1" />
   <row link="someLink" DATASOURCE="SomeSystem" obj_id="1" err_id="2" />')
,('<row UWI="123" Xcord="2.4" obj_id="1" err_id="1" /> 
   <row lUWI="122" Xcord="1.4" obj_id="1" err_id="2" />');


DECLARE @RowID INT=2;

DECLARE @columnNames AS NVARCHAR(MAX)=
STUFF(
        (
        SELECT DISTINCT ',' + t.c.value('local-name(.)', 'nvarchar(128)') 
        FROM #tbl AS tbl
        CROSS APPLY XMLVal.nodes('row/@*') as t(c) 
        WHERE ID=@RowID
        FOR XML PATH('')
        ),1,1,''
      ); 

DECLARE @cmd NVARCHAR(MAX)=
  N'SELECT p.*
    FROM
    (
    SELECT 
       t.c.value(''local-name(.)'', ''nvarchar(128)'') as [colname],
       t.c.value(''.'', ''nvarchar(128)'') as [value]
    FROM #tbl AS tbl
    CROSS APPLY XMLVal.nodes(''row/@*'') as t(c)
    WHERE ID=' + CAST(@RowID AS NVARCHAR(10)) +
    ') AS tbl
    PIVOT
    (
        MAX(value) FOR colname IN(' +  @columnNames + ')
    ) AS p';

    EXEC(@cmd);
GO
DROP TABLE #tbl;