如何在 SQL 中动态更改字符串的 XML 结构

Posted

技术标签:

【中文标题】如何在 SQL 中动态更改字符串的 XML 结构【英文标题】:How can I dynamically change the XML structure of a string in SQL 【发布时间】:2022-01-02 04:41:09 【问题描述】:

我需要一个 SQL 脚本,它可以从数据库 [varchar(max)] 中提取一个 XML 字符串,对其进行检查并更新它如果它适合特定情况。

假设我的 xml 格式如下:

<root>
  <level1>
    <level2>
      <level3 />
      <level3 />
    </level2>
  </level1>
  <level1>
    <level2>
      <level3>
        <level4>
          <level5>
            <level6 here="now is the time for XYZ">
              <options>
                <option this="that" />
                <option me="you" />
              </options>
            </level6>
          </level5>
        </level4>
      </level3>
    </level2>
  </level1>
  <level1>
    <level2>
      <level3>
        <level4>
          <level5>
            <level6 here="this one is not of interest">
              <options>
                <option this="that" />
                <option me="you" />
              </options>
            </level6>
          </level5>
        </level4>
      </level3>
    </level2>
  </level1>
  <level1>
    <level2>
      <level3>
        <level4>
          <level5>
            <level6 here="now is the time for ABC">
              <options>
                <option this="that" />
                <option me="you" />
                <option here="now" />
              </options>
            </level6>
          </level5>
        </level4>
      </level3>
    </level2>
  </level1>
</root>

所以,我要做的是更新所有名称为“level6”且具有名为“here”的属性的元素,其值开始为“now is the time”。所以,这应该只匹配上面的两个元素。

但是,这不是唯一的选择标准。选项列表不能包含&lt;option here="now" /&gt;。所以,这应该让我们只需要更新一个元素。

<level6 here="now is the time for XYZ">
    <options>
        <option this="that" />
        <option me="you" />
    </options>
 </level6>

对于那个元素,然后我想添加缺少的&lt;option here="now" /&gt;,这样就变成了:

<level6 here="now is the time for XYZ">
    <options>
        <option this="that" />
        <option me="you" />
        <option here="now" />
    </options>
 </level6>

所以,最终结果应该是:

 <root>
  <level1>
    <level2>
      <level3 />
      <level3 />
    </level2>
  </level1>
  <level1>
    <level2>
      <level3>
        <level4>
          <level5>
            <level6 here="now is the time for XYZ">
              <options>
                <option this="that" />
                <option me="you" />
                <option here="now" />      // <- this one new
              </options>
            </level6>
          </level5>
        </level4>
      </level3>
    </level2>
  </level1>
  <level1>
    <level2>
      <level3>
        <level4>
          <level5>
            <level6 here="this one is not of interest">
              <options>
                <option this="that" />
                <option me="you" />
              </options>
            </level6>
          </level5>
        </level4>
      </level3>
    </level2>
  </level1>
  <level1>
    <level2>
      <level3>
        <level4>
          <level5>
            <level6 here="now is the time for ABC">
              <options>
                <option this="that" />
                <option me="you" />
                <option here="now" />
              </options>
            </level6>
          </level5>
        </level4>
      </level3>
    </level2>
  </level1>
</root>

假设我可以将数据库中的数据读入字符串,并且我知道如何更新数据库,那么实际上就是如何在SQL(SQL Server)中操作xml字符串。

【问题讨论】:

你试过什么? :) 【参考方案1】:

您可以使用带有.modify 函数的XML DML(数据修改)来更改XML。

SET @xml.modify('
  insert <option here="now" />
  as last into
  ( /root/level1/level2/level3/level4/level5/level6
     [substring(@here, 1, 15) = "now is the time"]
     /options [not(/option[@here = "now"])]
   )[1]');

它的工作原理如下:

insert &lt;option here="now" /&gt; 这是我们要插入的值 as last into 追随所选节点的其他子节点 /root/level1/level2/level3/level4/level5/level6 这让我们知道level6 节点 [substring(@here, 1, 15) = "now is the time"] 表示节点具有以该值开头的 here 属性。您必须修改长度参数以匹配您正在比较的值。 XQuery 中没有LIKE /options [not(/option[@here = "now"])] 我们寻找具有 no option 子节点的 options 子节点,而子节点又具有 here="now" 属性 [1]第一个这样的节点

如果需要修改单个 XML 文档中的多个节点,则需要循环运行

DECLARE @i int = 20; --max nodes

WHILE @xml.exist('
  /root/level1/level2/level3/level4/level5/level6
     [substring(@here, 1, 15) = "now is the time"]
     /options [not(option[@here = "now"])]
   ') = 1
BEGIN

    SET @xml.modify('
      insert <option here="now" /> as last into
      ( /root/level1/level2/level3/level4/level5/level6
         [substring(@here, 1, 15) = "now is the time"]
         /options [not(option[@here = "now"])]
       )[1]');
     
    SET @i -= 1;
    IF @i = 0
        BREAK;
END;

你也可以对整张桌子这样做

DECLARE @i int = 20; --max nodes

WHILE EXISTS (SELECT 1
    FROM YourTable
    WHERE XmlColumn.exist('
      /root/level1/level2/level3/level4/level5/level6
         [substring(@here, 1, 15) = "now is the time"]
         /options [not(option[@here = "now"])]
       ') = 1)
BEGIN

    UPDATE t
    SET XmlColumn.modify('
      insert <option here="now" /> as last into
      ( /root/level1/level2/level3/level4/level5/level6
         [substring(@here, 1, 15) = "now is the time"]
         /options [not(option[@here = "now"])]
       )[1]')
    FROM YourTable t
    WHERE XmlColumn.exist('
      /root/level1/level2/level3/level4/level5/level6
         [substring(@here, 1, 15) = "now is the time"]
         /options [not(option[@here = "now"])]
       ') = 1;
     
    SET @i -= 1;
    IF @i = 0
        BREAK;
END;

对于非常大的数据集,使用 XQuery 重建整个 XML 可能会更快,并使用 Constructed XML 添加额外的节点。

db<>fiddle

【讨论】:

以上是关于如何在 SQL 中动态更改字符串的 XML 结构的主要内容,如果未能解决你的问题,请参考以下文章

如何创建基于当前日期/月份动态更改列顺序的 sql 表?

如何在 sql 脚本中动态创建和更改?

角度“动态文本”

在java中解析xml有哪几种方法

如何在数据库里 用like语句查询成语 !

如何在xml文件中申明dynamic元素类型