查看 XML 元素是否存在于具有特定值的文档中的任何级别

Joh*_*sey 8 xml sql-server t-sql

是否可以查询 XML 以查找特定元素是否具有特定值?例如,如果我想查看下面的 XML 在<ContactFName>.

但请注意,元素的位置可能会改变。在某些情况下,它可能在/root/MCTLocations/MCTLocation,或者可能跳到根下,或者出现在其他地方......

并且,是否可以参数化元素名称?

DECLARE @table TABLE (XmlCol XML)

INSERT INTO @table (XmlCol) VALUES ('
<root>
<MCTClientName>John</MCTClientName>
<MCTClientCity>Palm Beach</MCTClientCity>
<MCTLocations>
    <MCTLocation>
        <Address>1234 Main Street</Address>
        <ContactFName>Chris</ContactFName>
        <ContactLName>Brandt</ContactLName>
    </MCTLocation>
</MCTLocations>
</root>')

SELECT * FROM @table WHERE ??
Run Code Online (Sandbox Code Playgroud)

Sol*_*zky 15

为此,您希望使用.exist()XML 函数,因为它将返回一个 BIT(即布尔值)值,指示 XQuery 是否找到任何内容。

要处理元素的非静态位置,您可以使用*(表示它应该检查特定级别的所有节点,而不是其他级别)或//(表示它应该检查该级别及以下级别的所有节点)。

以下示例使用问题中的示例查询作为基础,并添加了一些测试用例以将元素放置在不同级别,并添加一个测试用例来更改名称以表明 XQuery 不只是选择所有内容。

测试设置(运行一次)

SET NOCOUNT ON;
CREATE TABLE #Table (ID INT NOT NULL, XmlCol XML);

INSERT INTO #Table (ID, XmlCol) VALUES (1, N'
<root>
<MCTClientName>John</MCTClientName>
<MCTClientCity>Palm Beach</MCTClientCity>
<MCTLocations>
    <MCTLocation>
        <Address>1234 Main Street</Address>
        <ContactFName>Chris</ContactFName>
        <ContactLName>Brandt</ContactLName>
    </MCTLocation>
</MCTLocations>
</root>');

INSERT INTO #Table (ID, XmlCol) VALUES (2, N'
<root>
<MCTClientName>John</MCTClientName>
<MCTClientCity>Palm Beach</MCTClientCity>
<MCTLocations>
    <MCTLocation>
        <Address>1234 Main Street</Address>
        <ContactFName>Chris</ContactFName>
        <ContactLName>Grandt</ContactLName>
    </MCTLocation>
</MCTLocations>
</root>');

INSERT INTO #Table (ID, XmlCol) VALUES (3, N'
<root>
<MCTClientName>John</MCTClientName>
<MCTClientCity>Palm Beach</MCTClientCity>
<MCTLocations>
    <MCTLocation>
        <Address>1234 Main Street</Address>
        <ContactFName>Chris</ContactFName>
    </MCTLocation>
</MCTLocations>
<ContactLName>Brandt</ContactLName>
</root>');

INSERT INTO #Table (ID, XmlCol) VALUES (4, N'
<root>
<MCTClientName>John</MCTClientName>
<MCTClientCity>Palm Beach</MCTClientCity>
<MCTLocations>
    <MCTLocation>
        <Address>1234 Main Street</Address>
        <ContactFName>Chris</ContactFName>
    </MCTLocation>
    <NewElement>
       <SubElement>
          <ContactLName>Brandt</ContactLName>
       </SubElement>
    </NewElement>
</MCTLocations>
</root>');

INSERT INTO #Table (ID, XmlCol) VALUES (5, N'
<root>
<MCTClientName>John</MCTClientName>
<MCTClientCity>Palm Beach</MCTClientCity>
<MCTLocations>
    <MCTLocation>
        <Address>1234 Main Street</Address>
        <ContactFName>Chris</ContactFName>
    </MCTLocation>
    <NewerElement>
       <ContactLName>Brandt</ContactLName>
    </NewerElement>
</MCTLocations>
</root>');

INSERT INTO #Table (ID, XmlCol) VALUES (6, N'
<root>
<MCTClientName>John</MCTClientName>
<MCTClientCity>Palm Beach</MCTClientCity>
<MCTLocations>
    <MCTLocation>
        <Address>1234 Main Street</Address>
        <ContactFName>Chris</ContactFName>
    </MCTLocation>
    <NewerElement>
    </NewerElement>
</MCTLocations>
</root>
<ContactLName>Brandt</ContactLName>
');
Run Code Online (Sandbox Code Playgroud)

测试 1(*代替节点名称)

这将检查指定级别的所有节点,在这种情况下,它位于<root>. 但它不会检查其他级别。

SELECT *
FROM   #Table tmp
WHERE  tmp.[XmlCol].exist(N'/*/ContactLName[text()="Brandt"]') = 1;
Run Code Online (Sandbox Code Playgroud)

返回ID值为 3 的行。

测试 2(*代替节点名称)

这将检查指定级别的所有节点,在这种情况下,它位于<root><MCTLocations>. 但它不会检查其他级别。

SELECT *
FROM   #Table tmp
WHERE  tmp.[XmlCol].exist(N'/root/MCTLocations/*/ContactLName[text()="Brandt"]') = 1;
Run Code Online (Sandbox Code Playgroud)

返回ID值为 1 和 5 的行。

测试 3(//代替节点名称)

这将检查指定级别开始的所有节点,在这种情况下<root><MCTLocations>,该级别刚好低于和低于

SELECT *
FROM   #Table tmp
WHERE  tmp.[XmlCol].exist(N'/root/MCTLocations//ContactLName[text()="Brandt"]') = 1;
Run Code Online (Sandbox Code Playgroud)

返回ID值为 1、4 和 5 的行。

测试 4(/**/代替节点名称)

SELECT *
FROM   #Table tmp
WHERE  tmp.[XmlCol].exist(N'*//ContactLName[text()="Brandt"]') = 1;

-- and:

SELECT *
FROM   #Table tmp
WHERE  tmp.[XmlCol].exist(N'//*/ContactLName[text()="Brandt"]') = 1;
Run Code Online (Sandbox Code Playgroud)

两者都返回ID值为1、3、4和 5 的行。

由于*作为单个节点的占位符,这些不会返回行 ID 6 ,因此允许的最高级别将低于<root>(或任何顶级节点)。

测试 5(//在顶层)

这将检查顶层开始的所有节点。

SELECT *
FROM   #Table tmp
WHERE  tmp.[XmlCol].exist(N'//ContactLName[text()="Brandt"]') = 1;
Run Code Online (Sandbox Code Playgroud)

返回ID值为1、3、4、5和 6 的行。

测试 6(在 XQuery 中对元素文本使用局部变量值)

DECLARE @Name NVARCHAR(50) = N'Brandt';

SELECT *
FROM   #Table tmp
WHERE  tmp.[XmlCol].exist(N'//ContactLName[text()=sql:variable("@Name")]') = 1;

SET @Name = N'Grandt';

-- exact same query, just different value in the variable
SELECT *
FROM   #Table tmp
WHERE  tmp.[XmlCol].exist(N'//ContactLName[text()=sql:variable("@Name")]') = 1;
Run Code Online (Sandbox Code Playgroud)

第一个查询返回ID值为1、3、4、5和 6 的行。

第二个查询返回ID值为 2 的行。

测试 7(在 XQuery 中使用函数和字符串字面量作为元素名称)

SELECT *
FROM   #Table tmp
WHERE  tmp.[XmlCol].exist(N'//.[local-name()="NewerElement"]') = 1;
Run Code Online (Sandbox Code Playgroud)

返回ID值为 5 和 6 的行。

测试 8(在 XQuery 中使用具有局部变量值的函数作为元素名称)

DECLARE @Node NVARCHAR(50) = N'SubElement';

SELECT *
FROM   #Table tmp
WHERE  tmp.[XmlCol].exist(N'//.[local-name()=sql:variable("@Node")]') = 1;
Run Code Online (Sandbox Code Playgroud)

返回ID值为 4 的行。

测试 9(将所有部分放在一起)

DECLARE @NodeName NVARCHAR(50) = N'ContactLName',
        @NodeText NVARCHAR(500) = N'Brandt';

SELECT *
FROM   #Table tmp
WHERE  tmp.[XmlCol].exist(N'//.[local-name()=sql:variable("@NodeName")]
   [text()=sql:variable("@NodeText")]') = 1;
Run Code Online (Sandbox Code Playgroud)

返回ID值为1、3、4、5和 6 的行。


一般 XML 注意:

XML 数据(在 SQL Server 中)被编码为 UTF-16 Little Endian,与NVARCHAR/相同NCHAR。因此,N当值实际上是 XML 时,最好用大写字母作为 sting 文字的前缀。

  • 这是迄今为止我在回复中得到的最好的一组例子。非常感谢! (3认同)
  • @JohnHennesey 不客气。谢谢你的赞美:-)。 (2认同)