为什么没有:
SELECT
SomeXmlColumn.nodes('/people/person') AS foo(b)
FROM MyTable
工作?
我几乎曾经在SQL Server中看到(或得到过)使用XPath查询要求您使用CROSS APPLY
将XML文档加入自身。
为什么?
SELECT
p.value('(./firstName)[1]', 'VARCHAR(8000)') AS firstName,
p.value('(./lastName)[1]', 'VARCHAR(8000)') AS lastName
FROM table
CROSS APPLY field.nodes('/person') t(p)
SELECT a.BatchXml.value('(Name)[1]', 'varchar(50)') AS Name,
a.BatchXml.value('(IDInfo/IDType)[1]', 'varchar(50)') AS IDType,
a.BatchXml.value('(IDInfo/IDOtherDescription)[1]', 'varchar(50)') AS IDOtherDescription
FROM BatchReports b
CROSS APPLY b.BatchFileXml.nodes('Customer') A(BatchXml)
WHERE a.BatchXml.exist('IDInfo/IDType[text()=3]')=1
SELECT b.BatchID,
x.XmlCol.value('(ReportHeader/OrganizationReportReferenceIdentifier)[1]','VARCHAR(100)') AS OrganizationReportReferenceIdentifier,
x.XmlCol.value('(ReportHeader/OrganizationNumber)[1]','VARCHAR(100)') AS OrganizationNumber
FROM Batches b
CROSS APPLY b.RawXml.nodes('/CasinoDisbursementReportXmlFile/CasinoDisbursementReport') x(XmlCol);
And even from MSDN Books Online:
SELECT nref.value('first-name[1]', 'nvarchar(32)') FirstName,
nref.value('last-name[1]', 'nvarchar(32)') LastName
FROM [XmlFile] CROSS APPLY [Contents].nodes('//author') AS p(nref)
他们都使用它。但没有人(甚至不是SQL Server联机丛书)解释了为什么需要它,它解决了什么问题,它做了什么,或者它是如何工作的。
即使是采用XML的最简单的例子:
<people>
<person><firstName>Jon</firstName><lastName>Johnson</lastName></person>
<person><firstName>Kathy</firstName><lastName>Carter</lastName></person>
<person><firstName>Bob</firstName><lastName>Burns</lastName></person>
</people>
并返回值:
FirstName LastName
========= ========
Jon Johnson
Kathy Carter
Bob Burns
需要加入:
SELECT
p.value('(./firstName)[1]', 'VARCHAR(8000)') AS firstName,
p.value('(./lastName)[1]', 'VARCHAR(8000)') AS lastName
FROM table
CROSS APPLY field.nodes('/person') t(p)
令人困惑的是它甚至没有使用它加入的表格,为什么需要它呢?
由于从未记录或解释过查询XML,希望我们现在可以解决这个问题。
所以让我们从一个实际的例子开始,因为我们想要一个实际的答案,这给出了一个实际的解释:
DECLARE @xml xml;
SET @xml =
'<people>
<person><firstName>Jon</firstName><lastName>Johnson</lastName></person>
<person><firstName>Kathy</firstName><lastName>Carter</lastName></person>
<person><firstName>Bob</firstName><lastName>Burns</lastName></person>
</people>';
;WITH MyTable AS (
SELECT @xml AS SomeXmlColumn
)
现在我们可以查询 psuedo 表:
首先,我需要人民。在真实 XML中,我可以轻松返回三行:
/people/person
这给出了包含三个节点的NodeList
:
<person><firstName>Jon</firstName><lastName>Johnson</lastName></person>
<person><firstName>Kathy</firstName><lastName>Carter</lastName></person>
<person><firstName>Bob</firstName><lastName>Burns</lastName></person>
在SQL Server中,查询相同:
SELECT
SomeXmlColumn.query('/people/person')
FROM MyTable
不会返回三行,而是返回包含三个节点的XML的一行:
<person>
<firstName>Jon</firstName>
<lastName>Johnson</lastName>
</person>
<person>
<firstName>Kathy</firstName>
<lastName>Carter</lastName>
</person>
<person>
<firstName>Bob</firstName>
<lastName>Burns</lastName>
</person>
显然这是不合适的,当我的最终目标是返回3 行时。我不得不将这三行分成三行。
我的目标是获得firstName
和lastName
。在XPath中我可以做类似的事情:
/people/person/firstName|/people/person/lastName
它给了我六个节点,虽然它们不是相邻的
<firstName>Jon</firstName>
<lastName>Johnson</lastName>
<firstName>Kathy</firstName>
<lastName>Carter</lastName>
<firstName>Bob</firstName>
<lastName>Burns</lastName>
在SQL Server中,我们尝试类似的东西
SELECT
SomeXmlColumn.query('/people/person/firstName') AS FirstName,
SomeXmlColumn.query('/people/person/lastName') AS LastName
FROM MyTable
它为我们提供了一个行,每列包含一个XML片段:
FirstName LastName
============================ ============================
<firstName>Jon</firstName> <lastName>Johnson</lastName>
<firstName>Kathy</firstName> <lastName>Carter</lastName>
<firstName>Bob</firstName> <lastName>Burns</lastName>
......现在我累了。我在four hours I spent asking yesterday's question之上花了三个小时写这个问题。我稍后会回到这个问题;当它在这里变凉时,我有更多精力去求助。
根本问题在于,无论我做什么,我都只会返回一行。我想要返回三行(因为有三个人)。 SQL Server 具有可以将XML行(称为节点)转换为SQL Server行(称为行)的函数。它是.nodes
函数:
当您想要将xml数据类型实例分解为关系数据时, nodes()方法很有用。它允许您识别将映射到新行的节点。
这意味着你打电话&#34;在.nodes
数据类型上使用XPath查询的xml
方法。过去在SQL Server中作为一行有三个节点的东西,回来(正确)为三个节点:
.nodes('/people/person') AS MyDerivedTable(SomeOtherXmlColumn)
从概念上讲,这会返回:
SomeOtherXmlColumn
------------------------------------------------------------------------
<person><firstName>Jon</firstName><lastName>Johnson</lastName></person>
<person><firstName>Kathy</firstName><lastName>Carter</lastName></person>
<person><firstName>Bob</firstName><lastName>Burns</lastName></person>
但如果您实际尝试使用它,它就无法运作:
DECLARE @xml xml;
SET @xml =
'<people>
<person><firstName>Jon</firstName><lastName>Johnson</lastName></person>
<person><firstName>Kathy</firstName><lastName>Carter</lastName></person>
<person><firstName>Bob</firstName><lastName>Burns</lastName></person>
</people>';
SELECT *
FROM @xml.nodes('/people/person') AS MyDervicedTable(SomeOtherXmlColumn)
给出错误:
Msg 493,Level 16,State 1,Line 8
列&#39; SomeOtherXmlColumn&#39;从nodes()方法返回的是不能直接使用的。它只能与四种XML数据类型方法之一,exists(),nodes(),query()和value()一起使用,或者在IS NULL和IS NOT NULL检查中使用。
我认为这是因为我不允许查看结果集(即不允许*
)。没问题。我将使用我原先使用的.query
:
SELECT SomeOtherXmlColumn.query('/') AS SomeOtherOtherXmlColumn
FROM @xml.nodes('/people/person') AS MyDervicedTable(SomeOtherXmlColumn)
返回行。但是,不是将节点列表拆分成行,而只是复制整个XML:
SomeOtherOtherXmlColumn
----------------------------------------
<people><person><firstName>Jon</firstName><lastName>Johnson</lastName></person><person><firstName>Kathy</firstName><lastName>Carter</lastName></person><person><firstName>Bob</firstName><lastName>Burns</lastName></person></people>
<people><person><firstName>Jon</firstName><lastName>Johnson</lastName></person><person><firstName>Kathy</firstName><lastName>Carter</lastName></person><person><firstName>Bob</firstName><lastName>Burns</lastName></person></people>
<people><person><firstName>Jon</firstName><lastName>Johnson</lastName></person><person><firstName>Kathy</firstName><lastName>Carter</lastName></person><person><firstName>Bob</firstName><lastName>Burns</lastName></person></people>
哪个有道理。我期待 SQL Server中的XPath查询表现得像XPath。但事后仔细阅读文档说不然:
nodes()方法的结果是一个包含原始XML实例的逻辑副本的行集。在这些逻辑副本中,每个行实例的上下文节点都设置为使用查询表达式标识的节点之一,以便后续查询可以相对于这些上下文节点进行导航。
xml
列前面的示例适用于xml
类型的变量。现在我们必须改进.nodes
函数以使用包含xml
列的表格:
SELECT
SomeXmlColumn.nodes('/people/person')
FROM MyTable
不,这不起作用:
Msg 227,Level 15,State 1,Line 8
&#34;节&#34;不是有效的函数,属性或字段。
虽然.nodes
是<{1}}数据类型的有效方法,但当您尝试在xml
上使用它时,它根本不起作用} 数据类型。在xml
数据类型上使用时,它也不起作用:
xml
Msg 208,Level 16,State 1,Line 8
无效的对象名称&#39; MyTable.SomeXmlColumn.nodes&#39;。
我认为是需要SELECT *
FROM MyTable.SomeXmlColumn.nodes('/people/person')
修饰符的原因。不是因为您要加入任何内容,而是因为SQL Server解析器将拒绝识别CROSS APPLY
,除非它前面带有关键字.nodes
:
cross apply
我们开始到达某个地方:
SELECT
'test' AS SomeTestColumn
FROM MyTable CROSS APPLY MyTable.SomeXmlColumn.nodes('/people/person') AS MyDerivedTable(SomeOtherXmlColumn)
因此,如果我们想要查看返回的XML:
SomeTestColumn
--------------
test
test
test
现在我们有三行。
SELECT
SomeOtherXmlColumn.query('/')
FROM (MyTable CROSS APPLY MyTable.SomeXmlColumn.nodes('/people/person') AS MyDerivedTable(SomeOtherXmlColumn))
似乎不是用于加入,而只是一个允许cross apply
工作的关键字
似乎SQL Server优化器只是拒绝接受任何
的使用.nodes
你必须实际使用:
.nodes
这就是它的原因。如果是这样的话 - 那没关系。这是规则。这导致了多年的混乱;以为我正在使用CROSS APPLY .nodes
运算符加入其他内容。
除了我相信它还有更多。不知何故,实际上必须发生cross apply
事件。但我无法看到 - 或为什么。
答案 0 :(得分:14)
查询:
SELECT x.i.value('(./text())[1]', 'VARCHAR(10)')
FROM MyTable.SomeXmlColumn.nodes('./people/person/firstName') AS x(i);
不起作用,原因与此查询不起作用的原因相同:
SELECT *
FROM Person.Person.FirstName;
但这样做:
SELECT FirstName
FROM Person.Person;
-
FROM 子句需要rowset,所以这是有效的,因为 nodes()返回rowset:
DECLARE @xml AS XML =
'<people>
<person><firstName>Jon</firstName><lastName>Johnson</lastName></person>
<person><firstName>Kathy</firstName><lastName>Carter</lastName></person>
<person><firstName>Bob</firstName><lastName>Burns</lastName></person>
</people>';
SELECT x.i.value('(./text())[1]', 'VARCHAR(10)')
FROM @xml.nodes('./people/person/firstName') AS x(i);
如果xml不是变量而是表中的值,我们首先需要从这个值中提取行,这就是 CROSS APPLY 派上用场的时候:
SELECT x.i.value('(./text())[1]', 'VARCHAR(10)')
FROM MyTable as t
CROSS APPLY
t.SomeXmlColumn.nodes('./people/person/firstName') AS x(i);
CROSS APPLY 运算符将右表达式应用于左表(MyTable)中的每条记录。
与'普通' CROSS APPLY 查询比较:
SELECT c.CustomerID, soh.TotalDue, soh.OrderDate
FROM Sales.Customer AS c
CROSS APPLY
(SELECT TOP(2) TotalDue, OrderDate
FROM Sales.SalesOrderHeader
WHERE CustomerID = c.CustomerID
ORDER BY TotalDue DESC) AS soh;
c.CustomerID是我们的t.SomeXmlColumn
答案 1 :(得分:5)
你问题的答案就在你的问题中。
nodes()方法的结果是行集
你不能这样做
WITH T(X) AS
(
SELECT 1
)
SELECT X, (SELECT 'A' AS Y UNION ALL SELECT 'B' AS Y)
FROM T
但你可以做到
WITH T(X) AS
(
SELECT 1
)
SELECT X, Y
FROM T
CROSS APPLY (SELECT 'A' AS Y UNION ALL SELECT 'B' AS Y) C
无论您在SELECT ... FROM T
列表中调用哪些函数,直接SELECT
都无法向结果集添加或减去行。这不是SQL的工作原理。
答案 2 :(得分:0)
我遇到了与您完全相同的问题。我无法获得行而不是XML表达式。我使用query('.').value(...)
解决了这个问题。
在您的代码上,我想应该是这样的:
SELECT
MyDerivedTable.SomeOtherXmlColumn.query('.').value('/people/person')
FROM MyTable CROSS APPLY MyTable.SomeXmlColumn.nodes('/people/person')
AS MyDerivedTable(SomeOtherXmlColumn)