【问题标题】:XPath 'contains()' requires a singleton (or empty sequence)XPath 'contains()' 需要一个单例(或空序列)
【发布时间】:2021-06-01 08:09:59
【问题描述】:

给定 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 中使用contains() 的正确方法是什么?或者这是错误的开始方式?

【问题讨论】:

  • 在提出问题时,您需要提供一个可重现的最小示例。请参考以下链接:stackoverflow.com/help/minimal-reproducible-example 请提供以下内容: (1) DDL 和样本数据填充,即 CREATE table(s) 加上 INSERT T-SQL 语句。 (2) 你需要做什么,即逻辑和你的代码尝试在 T-SQL 中实现它。 (3) 期望的输出,基于上面#1 中的样本数据。 (4) 你的 SQL Server 版本 (SELECT @@version;)

标签: sql-server xml xpath xquery


【解决方案1】:

您实际失败的数据库案例与您减少的有效示例案例之间的差异可能是不同的数据之一。

错误,

contains() 需要一个单例(或空序列)

表示您的DialName 元素之一具有多个文本节点子节点,而不是您期望的单个文本节点子节点。

您可以通过测试 DialNamestring-value 而不是其文本节点子节点来抽象出此类变化:

//Dial[contains(DialName, 'Bill')]/DialValue

另见

【讨论】:

  • 感谢您的反馈。我已将语句更改为: Dials.DialDetail.value('(//dial[contains(dialname, "Bill")]/dialvalue)[1]','VARCHAR(64)') AS BillTo 错误仍然存​​在。 (你会注意到我还修复了一些 XML 语法问题)
【解决方案2】:

这是在 MS SQL Server 中正确进行 XML 粉碎的方法。

您需要在 XQuery .nodes() 方法中应用过滤器。 .value() 方法仅用于实际值检索。 可以将 SQL Server 变量作为参数而不是硬编码的“Bill”值传递。

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    |
+----+--------+-------------------+-----------+

【讨论】:

    【解决方案3】:

    您几乎做对了,您只需要在 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

    【讨论】:

    • 这解决了它。谢谢你。在这种情况下,我确定不能有多个 DialName,所以我不认为强迫它只获得第一个。
    • 是的,它必须静态保证只有一个结果。 .value 的问题相同
    猜你喜欢
    • 2020-05-11
    • 1970-01-01
    • 2020-01-31
    • 2016-06-10
    • 1970-01-01
    • 1970-01-01
    • 2017-08-31
    • 1970-01-01
    • 2010-12-29
    相关资源
    最近更新 更多