跳转至

修改 XML — modify() 的三板斧:insert、replace value of、delete

这一章讲什么: XML 存进库之后总会变:加字段、改值、删节点。SQL Server 不用普通的 UPDATE 语句改 XML——那只能整列替换——而是给 XML 类型配了一个 modify() 方法, 里面装一门专管 XML 修改的小语言 XML DML。全章十个配方,学的就是这门语言的 三条命令和四个反复出现的坑。

1. 先看现象:为什么 UPDATE 改不了 XML 内部

普通 UPDATE 只能「整列换一个新文档」——想只改一个节点的值,就得读出整个 XML、在程序里改、再整列写回,又慢又容易丢并发。XML DML 解决的就是「就地外科手术」:只动目标节点,文档其余部分原样保留1。书里给它的定位很准:XML DML 像 T-SQL 的 DML(INSERT/UPDATE/DELETE)和 DDL(改结构)的合体——因为它改的不只是值,还有文档的结构(元素、属性本身)2。

2. 顶层全景:主线例子与三条命令

本章主线例子是一份产品描述的 XML,骨架全程不变:

<Root>
  <ProductDescription ProductID="1" ProductName="Road Bike">
    <Features>
      <Color>Silver</Color>
    </Features>
  </ProductDescription>
</Root>

(为讲解从原书各配方的示例中拼出的最小公共骨架;各配方的具体操作见下文。)

围绕它,三个 modify() 命令各管一件事:

  insert            加新节点(元素、属性),可指定位置、可加条件
  replace value of  改已有节点的值 —— 注意:不叫 update
  delete            删节点,支持 * 通配和 node test 清场

以及贯穿全章的四个坑:目标路径必须单例、命名空间要内部声明、属性语法自成一派、多元素要走变量。

3. 核心原理

3.1 insert:插元素,以及那条 Msg 2226 铁律

解决什么问题: 往已有 XML 里加一个新元素。

怎么做: 模式是 insert <新元素>值</新元素> into (目标路径)[1]。往 Features 里加保修条款:

SET @XMLDoc.modify('insert <Maintenance>3 year parts and labor extended maintenance is available</Maintenance>
                    into (/Root/ProductDescription/Features)[1]');

四个零件:insert 关键字、元素值模式、into 关键字、目标 XPath3。

为什么有效 / 铁律: 目标路径必须指到单个节点。漏写 [1],XML DML 解析器直接报 Msg 2226:「The target of 'insert' must be a single node, found 'element(Features,xdt:untyped) *'」——那个 * 就是在控诉你路径匹配了多个节点4。这条铁律贯穿全部三个命令,是本章复购率最高的报错。

边界: modify() 只收一个字符串参数,整个 XML DML 语句(含命名空间声明、条件逻辑)都塞在这一个字符串里5。

3.2 命名空间:在 modify() 内部声明,静默失败的陷阱

解决什么问题: 目标 XML 带命名空间时,路径怎么写才找得到节点。

怎么做: XML DML 字符串的第一个模式必须是命名空间声明,然后新元素和目标路径的每个元素都要挂前缀:

SET @XMLDoc.modify('declare namespace ns="http://schemas.microsoft.com/…";
                    insert <ns:Maintenance>…</ns:Maintenance>
                    into (/ns:Root/ns:ProductDescription/ns:Features)[1]');

前缀名自己起(遵守命名规范:无空格、首字符字母),声明之后新元素和目标路径两边都要用6。

为什么这是陷阱而非报错: 忽略了命名空间,解析器不抛任何错误,动作「看起来成功完成」——但 XML 原封未动。书里专门给了「忽略命名空间 → 静默无变化」的对照演示7。「不报错但什么都没发生」是 XML DML 里最阴的失败模式。

替代写法: 也可以用 T-SQL 侧的 WITH XMLNAMESPACES 声明默认命名空间——但有个怪异的前提:用了它,赋值就不能用 SET,必须改用 SELECT8。

3.3 插属性:一套自成一派的语法

解决什么问题: 给已有元素加属性(比如给 Maintenance 加 ProductModel="Mountain-100")。

怎么做: insert attribute 属性名 {"值"} into (目标元素)[1]。三个和元素不同的小规矩9:

  1. 多一对关键字 attribute;
  2. 属性名不加尖括号;
  3. 值放在花括号里、紧跟属性名。

批量与条件扩展: 多个属性用圆括号加逗号一次插入10;加条件用 XQuery 的 if … then … else(注意:else 分支不可省略——和 T-SQL 的 IF 不同,XML DML 的 if 是表达式,两个分支都要有),比如按 ProductID 是否等于 1 决定插 Road-150 还是 Mountain-10011。

一个语法细节: (路径)[1] 的圆括号位置是有语义的——括号套住整条路径,[1] 对整个路径的结果取单例;不加括号,引擎就期望每一级各带 [1](/Root[1]/ProductDescription[1]/…)12。

3.4 位置四关键词与多元素插入的曲线救国

解决什么问题一: 新元素要插在兄弟元素中的指定位置,不是随便 append。

怎么做: insert 有四个位置关键词13:

关键词 意思 语法差别
as first 插成第一个孩子 带 into,目标是父元素
as last 插成最后一个孩子 同上
before 插在某兄弟之前 不带 into,目标是那个兄弟元素
after 插在某兄弟之后 同上

解决什么问题二: 一次插多个兄弟元素。XML DML 的 insert 直接拒收元素列表(属性可以用逗号,元素不行)14。曲线救国:把多个元素存进一个 XML 变量,用 XQuery 扩展函数 sql:variable() 把整个变量内容当「一个节点块」插进去:

DECLARE @newElements xml = '<Warranty>1 year parts and labor</Warranty>
                            <Material>Aluminium</Material>
                            <Color>Silver</Color>
                            <BikeFrame>Strong long lasting</BikeFrame>';
SET @XMLDoc.modify('insert sql:variable(@newElements) into (/Root/ProductDescription/Features)[1]');

sql:variable() 是 SQL Server 给 XQuery 开的「引用 T-SQL 变量」后门——XML DML 自己碰不到 T-SQL 变量,全靠它传参15。

3.5 replace value of:改值的命令不叫 update

解决什么问题: 改已有元素的文本(把 <Color>Silver</Color> 改成 Black),或改属性值(ProductName 从 "Road Bike" 改成 "Mountain Bike")。

怎么做: replace value of (路径)[1] with "新值"。改元素值时,路径里要带上 text() 节点测试,指明要替换的是元素的文本内容16:

modify('replace value of (/Root/ProductDescription/Features/Color/text())[1] with "Black"')

改属性则不用 text()、属性名带 @:replace value of (/Root/ProductDescription/@ProductName)[1] with "Mountain Bike"17。

一个历史注脚(书里原话): 2005 年 XML 类型刚出时,「我认识的 SQL Server 行家全都困惑过」——大家预期的指令名是 update,结果它叫 replace value of。作者特意写这句,就是给后来者免惑18。

边界: 同样要求目标单例;with 后面是双引号字符串,不是 SQL 值——想传动态值,一样要走 sql:variable()。

3.6 delete:删节点与两种「清场」通配

解决什么问题: 删属性、删元素、删注释/处理指令/文本。

怎么做: delete 路径,不需要 into/with。删属性:delete /Root/ProductDescription/@ProductName;删元素:delete /Root/ProductDescription/Features/Color(多个同名元素时同样要 [1] 指定删哪个)19。删注释、处理指令、元素内文本,用 02 章见过的 node test:delete (…/comment())[1]、delete (…/processing-instruction())[1]、delete (…/Color/text())[1]20。

两种清场通配:

写法 效果
delete /Root/ProductDescription/@* 删掉该元素的所有属性
delete /Root/ProductDescription/Features/* 删掉该元素的所有孩子元素(Features 清空但保留壳)21

边界: 删除不存在的节点不报错(静默无操作)——这在「条件删」场景反而是优点,但要心里有数。

4. 作者的判断与证据

有代码级证据的: 全部报错原文都真实给出——Msg 2226(target must be a single node)、命名空间被忽略时「不报错但 XML 未变」的对照47;语法规则逐条列成模式清单3912。

作者的观察与调侃: 「replace value of 这个名字困惑过一整代 DBA」18;「insert 的花样很多,相比之下 update 很直白」18。这些是经验之谈,但对读者有真实的「解惑」价值。

本章的一个隐性主张: 书里没有明说但贯穿始终——XML DML 的设计明显借鉴了 T-SQL DML 的词汇(insert/delete/replace),却处处为了树的结构加了新规矩(单例、位置关键词、@、花括号)。学它的最好方式不是背语法,而是拿主线例子这条 XML,把十个操作各跑一遍——原书配方恰好就是这么编排的。

5. 边界与局限

  • 每次 modify() 只执行一条 XML DML 语句:一次调用里没法连续写 insert 再 delete(字符串里只允许一条命令)——多个操作要多次调用 modify() 或组合进一条(如 if 表达式)。书里没集中讲这个限制(补充,不在书里,来自 XML DML 语法)。
  • 改不了文档级的东西:XML 声明、DOCTYPE 不在 XML DML 射程内。
  • 性能未谈:对大文档频繁 modify() 的代价(整文档重写,和索引维护),本章没有量化——第 07 章的索引部分才补上拼图。
  • 动态值传递只靠 sql:variable():想从表里的列取值驱动修改,得先把列值挪进变量——配方的示例都是字面值。

6. 可带走的

本章主线例子,一行走完: <Features> 里只有 <Color>Silver</Color> 的文档 → insert 加 Maintenance([1] 铁律)→ 带命名空间版本(内部 declare,静默失败警告)→ insert attribute 加 ProductModel(花括号、无尖括号)→ if then else 按 ProductID 选值 → as first/as last/before/after 排位置 → sql:variable() 一次插四个兄弟 → replace value of 把 Silver 改 Black(text() 不可少)→ 改属性值(不用 text(),带 @)→ delete 单个节点、@* 清属性、/* 清孩子。

  1. 三条命令背下来: insert、replace value of(不叫 update)、delete218。
  2. 目标路径必须单例——漏 [1] 就是 Msg 2226,本章复购率最高的报错4。
  3. 命名空间在 modify() 字符串内部 declare;忘了不报错、XML 原样返回,比报错更坑7。
  4. 属性语法三异: 加 attribute 关键字、无尖括号、值在花括号9。
  5. 位置四关键词: as first/as last 配 into 指父元素;before/after 不带 into、指兄弟元素13。
  6. 一次插多元素走 sql:variable()——insert 不收元素列表,但收「装着元素列表的变量」15。
  7. if 是表达式不是语句:then/else 两个分支都必须给11。
  8. 改元素值带 text(),改属性值带 @ 不带 text()1617。
  9. 清场通配: @* 清全部属性,/* 清全部孩子元素21。
  10. 用了 WITH XMLNAMESPACES 就改用 SELECT 赋值,SET 不行8。

7. 原文地图

主题 原书章 原文位置
XML DML 定位与 modify 概述 Chapter 5: Modifying XML in SQL Server text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:74(搜「comparable to the T-SQL DML」)
insert 四零件与 Msg 2226 同上 text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:88(搜「keyword specifying」) · text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:100(搜「Msg 2226」)
modify 只收一个参数 同上 text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:104(搜「takes one argument」)
命名空间内部声明与静默失败 同上 text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:129(搜「declare namespace」) · text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:167(搜「has not changed」)
WITH XMLNAMESPACES 配 SELECT 同上 text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:209(搜「SELECT clause instead」)
插属性语法与批量 同上 text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:238(搜「differences as well」) · text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:264(搜「several attributes」)
if then else 条件插入 同上 text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:321(搜「if … then … else」)
位置四关键词 同上 text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:351(搜「four keywords」) · text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:407(搜「No into keyword」)
sql:variable() 多元素插入 同上 text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:423(搜「not supported」) · text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:473(搜「key part」)
replace value of 与命名掌故 同上 text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:502(搜「puzzled」) · text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:514(搜「with keyword」)
改属性值的差异 同上 text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:557(搜「prefixed with an @ symbol」)
delete 与两种通配 同上 text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:581(搜「simpler than the syntax」) · text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:590(搜「@」) · text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:638(搜「/」)
node test 删除 同上 text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:655(搜「node tests」)
本章小结 同上 text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:678(搜「three primary directives」)

  1. 出处:「Chapter 5: Modifying XML in SQL Server」第 3–19 段(text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:5,搜「real-world scenarios」)。章首说明:许多场景「require more than just retrieval – they require the ability to change the XML content dynamically」,并列出增、删、改三类操作。 ↩

  2. 出处:「Chapter 5: Modifying XML in SQL Server」第 73–81 段(text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:74,搜「comparable to the T-SQL DML」)。原文:「The XML modify() method's XML DML language is comparable to the T-SQL DML INSERT, UPDATE, and DELETE methods, but with many additional options」「the modify() method can be logically considered a combination of a subset of T-SQL's DML and DDL languages」。 ↩↩

  3. 出处:「Chapter 5: Modifying XML in SQL Server」第 87–94 段(text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:88,搜「keyword specifying」)。四个模式:insert 关键字、元素值模式、「into is the keyword in the target XPath path」、目标路径。 ↩↩

  4. 出处:「Chapter 5: Modifying XML in SQL Server」第 94–103 段(text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:100,搜「Msg 2226」)。原文:「a singleton instance is a required component for the target XPath」;报错:「The target of 'insert' must be a single node, found 'element(Features,xdt:untyped) *'」。 ↩↩↩

  5. 出处:「Chapter 5: Modifying XML in SQL Server」第 104–106 段(text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:104,搜「takes one argument」)。原文:「The modify(@xml:dml) method takes one argument. Therefore, all @xml:dml patterns sent to the method are submitted as a single string value」。 ↩

  6. 出处:「Chapter 5: Modifying XML in SQL Server」第 168–183 段(text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:169,搜「first pattern」)。原文:「the first pattern for the XML instance with the namespace must be the namespace declaration」;前缀命名规范见第 174–178 段。 ↩

  7. 出处:「Chapter 5: Modifying XML in SQL Server」第 143–166 段(text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:146,搜「ignores the」)。原文:「If the modify() method ignores the XML namespace, the XQuery won't find the target XPath path, resulting in no change to the XML」;结果:「the XML instance returned has not changed」。 ↩↩↩

  8. 出处:「Chapter 5: Modifying XML in SQL Server」第 208–210 段(text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:209,搜「SELECT clause instead」)。原文:「When you are implementing the WITH XMLNAMESPACES clause, then the SET operator will not work. You must implement the SELECT clause instead」。 ↩↩

  9. 出处:「Chapter 5: Modifying XML in SQL Server」第 236–251 段(text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:238,搜「differences as well」)。原文:「the insert statement has an additional attribute option」「the attribute is not surrounded by angle brackets」「the attribute's value is placed into curly brackets with double quotes immediately following the attribute name」。 ↩↩↩

  10. 出处:「Chapter 5: Modifying XML in SQL Server」第 264–271 段(text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:264,搜「several attributes」)。原文:「Open parentheses after the insert directive. List the attributes separated by a comma. Close the parentheses」。 ↩

  11. 出处:「Chapter 5: Modifying XML in SQL Server」第 320–343 段(text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:321,搜「if … then … else」)。原文:「you can use the if … then … else construct within the modify() method」;ProductID=1 → Road-150,其余 → Mountain-100。 ↩↩

  12. 出处:「Chapter 5: Modifying XML in SQL Server」第 258–261 段(text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:259,搜「applies to every step」)。原文:「Wrapping the XPath path in parentheses and then putting the numeric positional predicate on the end means that the singleton applies to every step in the path」。 ↩↩

  13. 出处:「Chapter 5: Modifying XML in SQL Server」第 351–360 段(text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:351,搜「four keywords」)。原文:「The modify() method has four keywords…as first, as last, after, before」;before/after「No into keyword implemented」见第 405–411 段(text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:407,搜「No into keyword」)。 ↩↩

  14. 出处:「Chapter 5: Modifying XML in SQL Server」第 417–425 段(text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:423,搜「multiple elements are not supported」)。原文:「Unlike attributes, where you can add several attributes and their values by separating them with a comma, multiple elements are not supported in a direct insert within the modify() method」。 ↩

  15. 出处:「Chapter 5: Modifying XML in SQL Server」第 446–474 段(text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:473,搜「key part」)。原文:「The XQuery extension function sql:variable() is a key part of this simple solution」;变量建议用 XML 类型保持一致(第 451–454 段)。 ↩↩

  16. 出处:「Chapter 5: Modifying XML in SQL Server」第 499–521 段(text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:512,搜「text() function」)。改元素值的三要件:replace value of 语句、路径+text()+单例、with 后双引号新值。 ↩↩

  17. 出处:「Chapter 5: Modifying XML in SQL Server」第 546–558 段(text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:557,搜「prefixed with an @ symbol」)。原文:「The major difference between modifying the element and the attribute is that the attribute must be prefixed with an @ symbol and the text() node test is not needed」。 ↩↩

  18. 出处:「Chapter 5: Modifying XML in SQL Server」第 500–507 段(text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:502,搜「puzzled」)。原文:「many DBAs and SQL Server Developers were puzzled, at least all SQL Server professionals that I know. We expected a directive name of "update" or so; however, the replace value of directive updates the XML instance elements and attributes」。 ↩↩↩↩

  19. 出处:「Chapter 5: Modifying XML in SQL Server」第 580–636 段(text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:629,搜「singleton is required」)。原文:「A singleton is required to delete a specific element when the XML has several elements with the same name」;例:delete /Root/ProductDescription/Features/Color[1] 删掉 Silver、留下 Blue。 ↩

  20. 出处:「Chapter 5: Modifying XML in SQL Server」第 654–669 段(text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:655,搜「node tests」)。原文:「To delete items like comments and processing instructions, or even the text within an element, you could use node tests with delete」。 ↩

  21. 出处:「Chapter 5: Modifying XML in SQL Server」第 589–603 段(text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:590,搜「@」)与第 637–647 段(text/09-ch05-chapter-5-modifying-xml-in-sql-server.txt:638,搜「/」)。原文:「the XPath should have the destination element path followed by "/@"」「To remove all child elements from the parent element, the XPath should point to the parent element plus "/"」。 ↩↩