给定XML:
<Dial>
<DialID>
24521
</DialID>
<DialName>
Base Price
</DialName>
</Dial>
<Dial>
<DialID>
24528
</DialID>
<DialName>
Rush Options
</DialName>
<DialValue>
1.5
</DialValue>
</Dial>
<Dial>
<DialID>
24530
</DialID>
<DialName>
Bill Rush Charges
</DialName>
<DialValue>
School
</DialValue>
</Dial>
我可以在我的xpath:中使用contains()函数
//Dial[DialName[contains(text(), 'Bill')]]/DialValue
检索我想要的值:
School
上面的XML存储在SQL数据库的一个字段中,所以我使用.value方法从该字段中进行选择。
SELECT Dials.DialDetail.value('(//Dial[DialName[contains(text(), "Bill")]]/DialValue)[1]','VARCHAR(64)') AS BillTo
FROM CampaignDials Dials
我似乎搞不懂语法。。。xpath按预期工作(在Oxygen和其他地方进行了测试),但当我在.value()方法的XQuery参数中使用它时,我会得到一个错误:
Started executing query at Line 1
Msg 2389, Level 16, State 1, Line 36
XQuery [Dials.DialDetail.value()]: 'contains()' requires a singleton (or empty sequence), found operand of type 'xdt:untypedAtomic *'
Total execution time: 00:00:00.004
我试过单引号和双引号的不同变体,但都没有效果。错误指的是属性的XPath数据类型,但我没有检索属性;我正在获取文本值。如果我用//Dial[DialName[contains(text(),'Bill')]]/DialValue/text()键入响应,我会收到同样的错误。
当在XML.value()
方法中使用contains()
时,在XQuery中使用它的正确方式是什么?或者这是一开始就错误的做法?
你几乎是对的,你只需要text()
函数上的[1]
来保证一个值。
出于性能原因,您还应该在要拉出的实际节点上使用text()
。
此外,//
可能效率低下,所以只有在真正需要递归下降时才使用它。您可以使用/*/
来获取任何名称的第一个节点。
SELECT
Dials.DialDetail.value(
'(//Dial[DialName[contains(text()[1], "Bill")]]/DialValue/text())[1]',
'VARCHAR(64)') AS BillTo
FROM CampaignDials Dials
正如Yitzhak Kabinsky所指出的,这只会为表的每一行获得一个值,如果您想将XML本身分解成行,则需要.nodes
。
失败的实际数据库案例和有效的简化示例案例之间的差异可能是不同的数据之一。
错误,
contains()
需要单例(或空序列)
表示DialName
元素中的一个元素具有多个子文本节点,而不是您所期望的单个文本节点。
您可以通过测试DialName
的字符串值而不是其文本节点子级来抽象出这些变化:
//Dial[contains(DialName, 'Bill')]/DialValue
另请参阅
- 在XPath中测试text()节点与字符串值
以下是如何在MS SQL Server中正确执行XML切碎。
您需要在XQuery.nodes()
方法中应用filter。.value()
方法仅用于实际值检索。可以将SQL Server变量作为参数而不是硬编码";比尔;价值
SQL
-- DDL and sample data population, start
DECLARE @tbl TABLE (ID INT IDENTITY PRIMARY KEY, DialDetail XML);
INSERT INTO @tbl (DialDetail) VALUES
(N'<Dial>
<DialID>24521</DialID>
<DialName>Base Price</DialName>
</Dial>
<Dial>
<DialID>24528</DialID>
<DialName>Rush Options</DialName>
<DialValue>1.5</DialValue>
</Dial>
<Dial>
<DialID>24530</DialID>
<DialName>Bill Rush Charges</DialName>
<DialValue>School</DialValue>
</Dial>');
-- DDL and sample data population, end
SELECT ID
, c.value('(DialID/text())[1]', 'INT') AS DialID
, c.value('(DialName/text())[1]', 'VARCHAR(30)') AS DialName
, c.value('(DialValue/text())[1]', 'VARCHAR(30)') AS DialValue
FROM @tbl CROSS APPLY DialDetail.nodes('/Dial[contains((DialName/text())[1], "Bill")]') AS t(c);
输出
+----+--------+-------------------+-----------+
| ID | DialID | DialName | DialValue |
+----+--------+-------------------+-----------+
| 1 | 24530 | Bill Rush Charges | School |
+----+--------+-------------------+-----------+