修改 XML — modify() 的三板斧:insert、replace value of、delete
修改 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:
- 多一对关键字 attribute;
- 属性名不加尖括号;
- 值放在花括号里、紧跟属性名。
批量与条件扩展: 多个属性用圆括号加逗号一次插入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 单个节点、@* 清属性、/* 清孩子。
- 三条命令背下来: insert、replace value of(不叫 update)、delete218。
- 目标路径必须单例——漏
[1]就是 Msg 2226,本章复购率最高的报错4。 - 命名空间在 modify() 字符串内部 declare;忘了不报错、XML 原样返回,比报错更坑7。
- 属性语法三异: 加 attribute 关键字、无尖括号、值在花括号9。
- 位置四关键词: as first/as last 配 into 指父元素;before/after 不带 into、指兄弟元素13。
- 一次插多元素走
sql:variable()——insert 不收元素列表,但收「装着元素列表的变量」15。 - if 是表达式不是语句:then/else 两个分支都必须给11。
- 改元素值带 text(),改属性值带 @ 不带 text()1617。
- 清场通配:
@*清全部属性,/*清全部孩子元素21。 - 用了 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」) |
Footnotes
-
出处:「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」,并列出增、删、改三类操作。 ↩ -
出处:「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」。 ↩ ↩2 -
出处:「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」、目标路径。 ↩ ↩2 -
出处:「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) *'」。 ↩ ↩2 ↩3 -
出处:「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」。 ↩ -
出处:「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 段。 ↩ -
出处:「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」。 ↩ ↩2 ↩3 -
出处:「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」。 ↩ ↩2 -
出处:「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」。 ↩ ↩2 ↩3 -
出处:「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」。 ↩ -
出处:「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。 ↩ ↩2 -
出处:「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」。 ↩ ↩2 -
出处:「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」)。 ↩ ↩2 -
出处:「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」。 ↩ -
出处:「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 段)。 ↩ ↩2 -
出处:「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 后双引号新值。 ↩ ↩2 -
出处:「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」。 ↩ ↩2 -
出处:「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」。 ↩ ↩2 ↩3 ↩4 -
出处:「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。 ↩ -
出处:「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」。 ↩ -
出处:「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 "/"」。 ↩ ↩2