使用 XQuery 过滤掉一些孩子,但保留父母

Cad*_*oux 9 sql-server xquery

SET NOCOUNT ON;
DECLARE @xml AS Xml = '<a><b>bbb</b><c>ccc</c><d>ddd</d></a>';
SELECT @xml;
SELECT @xml.query('/a/*[self::b or self::c]');
SET @xml.modify('delete /a/d');
SELECT @xml;
Run Code Online (Sandbox Code Playgroud)

给出以下结果集

原来的:

<a><b>bbb</b><c>ccc</c><d>ddd</d></a>
Run Code Online (Sandbox Code Playgroud)

过滤以排除非 (b|c) - 但缺少父母:

<b>bbb</b><c>ccc</c>
Run Code Online (Sandbox Code Playgroud)

我想要什么(可用于删除步骤,但不可用于 .query):

<a><b>bbb</b><c>ccc</c></a>
Run Code Online (Sandbox Code Playgroud)

是否可以在 XQuery 中保留父项?

i-o*_*one 9

您可以尝试以下

SELECT @xml.query('
    element a {
        for $node in /a/*[local-name() != "d"]
            return $node
    }');
Run Code Online (Sandbox Code Playgroud)

但是如果您的真实 XML 更复杂并且您必须在更深的嵌套级别排除节点,则它可能不合适。


老实说,我不认为 XQuery 是处理此类任务的好工具。本质上,我们有 XML,我们需要在其中的某个地方删除一个节点,即我们需要对其进行转换。我认为更合适的工具是 XSL 转换。不幸的是,SQL Server 没有内置的 XSLT 功能(不过可以通过 SQLCLR 函数添加)。

这就是用于解决此任务的 XSL 正文的外观

<!-- Skip "d" under "a" -->
<xsl:template match="a/d" />

<!-- Apply identity transform to other nodes -->
<xsl:template match="@*|node()">
  <xsl:copy>
    <xsl:apply-templates select="@*|node()" />
  </xsl:copy>
</xsl:template>
Run Code Online (Sandbox Code Playgroud)

它适用于a驻留在 XML 中任何位置的元素,而不仅仅是在根中。

用于执行非参数化 XSL 转换的 SQLCLR 函数可以像几行 C# 代码一样简单

[SqlFunction(Name = "XslTransform")]
public static SqlXml XslTransform(SqlXml xml, SqlXml xsl)
{
    if (xml.IsNull || xsl.IsNull)
        return SqlXml.Null;

    var xslt = new XslCompiledTransform();
    using (var xr = xsl.CreateReader())
        xslt.Load(xr);

    var xws = new XmlWriterSettings
    {
        Encoding = Encoding.Unicode,
        OmitXmlDeclaration = true
    };

    var output = new MemoryStream();
    using (var xw = XmlWriter.Create(output, xws))
    using (var xr = xml.CreateReader())
    {
        xslt.Transform(xr, null, xw);
        xw.Flush();
    }

    output.Seek(0, SeekOrigin.Begin);

    return new SqlXml(output);
}
Run Code Online (Sandbox Code Playgroud)

它应该在数据库中声明,如

CREATE FUNCTION SQLCLR.XslTransform
(
    @xml xml,
    @xsl xml
)
RETURNS xml
AS EXTERNAL NAME [AssemblyName].[ClassName].[XslTransform];
GO
Run Code Online (Sandbox Code Playgroud)

然后它可以用作

DECLARE
    @xml xml = N'(Your XML goes here)',
    @xsl xml = N'(Your XSL goes here)';

SELECT SQLCLR.XslTransform(@xml, @xsl);
Run Code Online (Sandbox Code Playgroud)