我需要能够从SQL输出分层XML,但我不想为每个所需的架构创建类似的功能。
例如,给定的数据如下:
+----+----------+----------------------------------------------------------+
| ID | ParentID | XML |
+----+----------+----------------------------------------------------------+
| 1 | NULL | <root/> |
| 2 | 1 | <A someattr="whatever">some text</A> |
| 3 | 1 | <B someattr="stuff">more text <and><elements/></and></B> |
| 4 | 3 | <B1>Child of B</B1> |
| 5 | 4 | <B1A/> |
| 6 | 2 | <Child_Of-A/> |
+----+----------+----------------------------------------------------------+
我们应该有一个如下所示的输出:
<root>
<A someattr="whatever">some text<Child_Of-A /></A>
<B someattr="stuff">more text<and><elements /></and>
<B1>Child of B<B1A />
</B1>
</B>
</root>
(或在语义上相同)
互联网上有很多文章解释了如何非动态地执行此操作,但是我找不到任何通用的解决方案。
如何在不为每个模式创建新功能(或一组新功能)的情况下完成此操作?
答案 0 :(得分:0)
事实证明,尽管N.B.对于大型数据集,这可能相当慢(我的实际数据集只有191个节点,但仍需要大约6秒钟)
对于大型数据集,可能需要一种非动态方法。
首先,我们创建一个表类型来形式化所需的输入结构。
CREATE TYPE dbo.HierachicalXMLInput AS TABLE
(
[ID] int,
[ParentID] int,
[XML] xml
)
第一个函数仅创建层次
CREATE FUNCTION dbo.fnHierachicalXMLHierachy
( @Data dbo.HierachicalXMLInput READONLY,
@ParentID int = NULL
) RETURNS XML AS
BEGIN
DECLARE @Out xml
;
WITH XMLNAMESPACES ('ChangeMetoSomeUniqueNamepsace' as h)
SELECT @Out = (
SELECT [ID] AS [@ID],
dbo.fnHierachicalXMLHierachy(@Data, [ID]) AS [*]
FROM @Data
WHERE ([ParentID]=@ParentID) Or ([ParentID] IS NULL AND @ParentID IS NULL)
FOR XML PATH ('h:N')
)
RETURN @Out
END
这将输出类似的内容(为清楚起见,省略了冗余的名称空间声明)
<h:N xmlns:h="ChangeMetoSomeUniqueNamepsace" ID="1">
<h:N ID="2">
<h:N ID="6" />
</h:N>
<h:N ID="3">
<h:N ID="4">
<h:N ID="5" />
</h:N>
</h:N>
</h:N>
然后,我们可以将其与原始数据结合起来以获取所需的输出:
CREATE FUNCTION dbo.fnHierachicalXML
( @Data dbo.HierachicalXMLInput READONLY
) RETURNS XML AS
BEGIN
DECLARE @XML xml = dbo.fnHierachicalXMLHierachy(@Data, NULL)
DECLARE @NodeID int
-- we need to replace the placeholder nodes one by one.
-- we should only replace nodes that do not have placeholder children
-- i.e. we start deepest first
-- find the first node to replace and get it's ID
; WITH XMLNAMESPACES ('ChangeMetoSomeUniqueNamepsace' as h)
SELECT @NodeID= @XML.value('(//h:N[not(h:N)])[1]/@ID', 'int')
DECLARE @Original xml
WHILE @NodeID IS NOT NULL -- there is something to replace
BEGIN
--get the actual XML that is supposed to go where the placeholder currently is
SELECT @Original=[XML]FROM @Data WHERE ID=@NodeID
-- insert the desired node after the placeholder
SET @XML.modify('declare namespace h="ChangeMetoSomeUniqueNamepsace";
insert sql:variable("@Original") after (//h:N[@ID=sql:variable("@NodeID") and not(h:N)])[1]')
-- if this isn't the first time around then the placeholder's placeholder children have already been replace with actual content.
-- so insert the placeholder's content into the real node
SET @XML.modify('
declare namespace h="ChangeMetoSomeUniqueNamepsace";
insert (//h:N[@ID=sql:variable("@NodeID") and not(h:N)])[1]/*
as last into
((//h:N[@ID=sql:variable("@NodeID") and not(h:N)])[1]/../*[. >>
(//h:N[@ID=sql:variable("@NodeID") and not(h:N)])[1]
])[1]
')
-- in the above: x/../*[. >> x] is basically the same (as x::following-sibling:*)
-- sadly it seems SQL's XML DML doesn't support axes like following-sibling, though it's not clear why this is so.
-- Instead we use the more convoluted syntax
-- finally, delete the placeholder
SET @XML.modify('
declare namespace h="ChangeMetoSomeUniqueNamepsace";
delete (//h:N[@ID=sql:variable("@NodeID") and not(h:N)])[1]
')
-- and find the next placeholder to replace
;WITH XMLNAMESPACES ('ChangeMetoSomeUniqueNamepsace' as h)
SELECT @NodeID = @XML.value('(//h:N[not(h:N)])[1]/@ID', 'int')
END
RETURN @XML
END
然后我们可以像这样使用它:
DECLARE @dt AS dbo.HierachicalXMLInput
INSERT INTO @dt SELECT 1, NULL, '<root></root>'
INSERT INTO @dt SELECT 2, 1, '<A someattr="whatever">some text</A>'
INSERT INTO @dt SELECT 3, 1, '<B someattr="stuff">more text <and><elements/></and></B>'
INSERT INTO @dt SELECT 4, 3, '<B1>Child of B</B1>'
INSERT INTO @dt SELECT 5, 4, '<B1A/>'
INSERT INTO @dt SELECT 6, 2, '<Child_Of-A/>'
-- SELECT * FROM @dt
DECLARE @XML xml = dbo.fnHierachicalXML (@dt)
SELECT @XML
并获得所需的输出:
<root>
<A someattr="whatever">some text<Child_Of-A /></A>
<B someattr="stuff">more text <and><elements /></and><B1>Child of B<B1A /></B1></B>
</root>