我有一个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;
答案 0 :(得分:0)
在您详细了解目标的情况下,使用XPath直接解决XML的元素和属性会更容易。
对于未知XML的通用读取,您的方法是完全正确的 - 但这里很复杂。您必须先按行逐行读取属性,然后必须执行某种PIVOT
或GROUP 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;