如果我执行:
INSERT INTO dst
SELECT blah
FROM src
CROSS APPLY xmlcolumn.nodes('blah')
如果dst有一个标识列,是否可以确定该标识列的顺序与原始xml文档中节点的顺序相匹配?
我认为答案是否定的,没有保证。为了确保顺序能够保持不变,需要在枚举节点的同时从XML中提取一些顺序信息。
如果我执行:
INSERT INTO dst
SELECT blah
FROM src
CROSS APPLY xmlcolumn.nodes('blah')
如果dst有一个标识列,是否可以确定该标识列的顺序与原始xml文档中节点的顺序相匹配?
我认为答案是否定的,没有保证。为了确保顺序能够保持不变,需要在枚举节点的同时从XML中提取一些顺序信息。
虽然在执行计划中无法明确看到,但由nodes()
方法返回的id
列是一个varbinary(900)
OrdPath,它封装了原始xml文档的顺序。
Mikael Eriksson在相关问题“`nodes()`方法是否保留文档顺序?”上提供的解决方案依赖于OrdPath来提供ORDER BY
子句,以确定如何为INSERT
分配身份值。
以下是稍微更紧凑的使用:
CREATE TABLE #T
(
ID integer IDENTITY,
Fruit nvarchar(10) NOT NULL
);
DECLARE @xml xml =
N'
<Fruits>
<Apple />
<Banana />
<Orange />
<Pear />
</Fruits>
';
INSERT #T
(Fruit)
SELECT
N.n.value('local-name(.)', 'nvarchar(10)')
FROM @xml.nodes('/Fruits/*') AS N (n)
ORDER BY
ROW_NUMBER() OVER (ORDER BY N.n);
SELECT
T.ID,
T.Fruit
FROM #T AS T
ORDER BY
T.ID;
的使用方法目前未有官方文档,但原理是可行的:
ROW_NUMBER
按 OrdPath* 计算序列值。ORDER BY
子句使用行号序列。ORDER BY
分配标识值给行。需要明确的是,即使使用并行处理,这一点仍然成立。正如 Mikael 所说,使用 id
在 ROW_NUMBER
中存在疑问,因为文档未记录 id
作为 OrdPath。
计划中没有显示顺序,但使用TF 8607的优化器输出包含:
ScaOp_SeqFunc row_number order[CALC:QCOL: XML Reader with XPath filter.id ASC]
在当前实现的.nodes
下,XML节点按文档顺序生成。结果总是使用嵌套循环与原始数据连接,也总是按顺序运行。
此外,插入通常是串行的(除非在非常特定的情况下它变为并行,通常是在有空表时,并且永远不会生成一个IDENTITY
值)。
因此,服务器没有理由以与文档顺序不同的顺序返回行。您可以从这个代码片段中看到发生了什么。
话虽如此,不能保证.nodes
实现不会改变,或者将来可能会并行插入,因为这两种情况都没有被记录在任何地方作为保证。因此,在没有明确的ORDER BY
的情况下,您不应该依赖它,同时也没有可以排序的列。
使用
ORDER BY
可以保证其正确性。文档中提到:“使用SELECT
和ORDER BY
填充行的INSERT
查询保证标识值的计算方式,但并不保证插入行的顺序。”
即使像一些人建议的那样使用ROW_NUMBER
,也不能保证正确性。真正的解决方法是直接从XQuery获取文档顺序。
问题在于,SQL Server版本的XQuery不允许将position(.)
用作结果,只能用作谓词。相反,您可以使用涉及<<
位置运算符的Hack方法。
例如:
SELECT T.X.value('text()[1]', 'nvarchar(100)') as RowLabel,
T.X.value('let $i := . return count(../*[. << $i]) + 1', 'int') as RowNumber
FROM src
CROSS APPLY xmlcolumn.nodes('blah') as T(X);
这段代码的作用如下:
.
赋值给变量$i
../*
$i
之前的节点 [. << $i]