SQL XML查询脚本逻辑解析求助:解读含FOR XML PATH、ELEMENTS XSINIL等函数的语句
你的SQL脚本逻辑全解析
嘿,我来一步步帮你拆解这个脚本,把你的疑问全搞定!
先搞懂整个脚本的核心作用
这个脚本的目的是把dbo.tablename表的所有数据,转换成**一行表头(列名用^^分隔) + 每一行数据(列值用^^分隔)**的格式,最终输出的每一行都是一个用^^串联的字符串。
逐个解答你的疑问
1. 仅单表为啥要用CROSS APPLY?
这里的CROSS APPLY不是用来关联两个物理表的,它是处理XML数据的特殊技巧!
前面的select T.* for xml path(''), type, ELEMENTS XSINIL已经把原表的一行数据转换成了XML格式(比如<列名1>值1</列名1><列名2>值2</列名2>),得到的T1.N是一个XML类型的列。CROSS APPLY配合nodes()函数,能把这个XML里的每个子节点(对应原表的每一列)拆成单独的行,这样我们才能逐个处理每个列名或列值,最后再把它们拼接成一个完整的字符串。
2. 表中没有nodes列,“nodes”来自哪儿?
nodes不是表的列,它是XML数据类型的内置函数!
写法T1.N.nodes('/*')里,T1.N是XML类型的列,.nodes()函数的作用是:根据括号里的XPath表达式(这里是/*),把XML中匹配的节点转换成一个行集。后面的as T2(N)是给这个行集起别名T2,行集里的列叫N,这个N就是每个XML节点的对象。
3. fn:local-name(.)[1]是什么意思?
这是XQuery的函数,用来获取XML节点的本地名称(也就是节点的名字,对应原表的列名):
fn:local-name():函数本身,专门提取XML节点的名称(.):代表当前正在处理的节点(就是T2.N对应的那个列节点)[1]:取第一个匹配的结果(这里每个节点只有一个名称,加不加都不影响,但写法更严谨)
所以整个表达式就是获取当前XML节点的名字,也就是原表的列名。
4. T1.N.Nodes('/*')具体含义?
拆解一下:
T1.N:是前面select T.* for xml path(''), type, ELEMENTS XSINIL生成的XML列,比如原表有id、name两列,这个XML大概是<id>1</id><name>张三</name>.nodes('/*'):/*是XPath表达式,意思是“匹配XML里的所有顶层节点”(也就是每个列对应的节点)
所以T1.N.Nodes('/*')就是把这个XML里的每个列节点,拆成单独的行,每一行对应一个列的XML节点,方便后续逐个处理。
脚本两部分的详细拆解
第一部分:生成表头(列名)
select top 1 ( select '^^'+T2.N.value('fn:local-name(.)[1]', 'varchar(max)') from ( select T.* for xml path(''), type, ELEMENTS XSINIL ) as T1(N) cross apply T1.N.nodes('/*') as T2(N) for xml path(''), type, ELEMENTS XSINIL ).value('substring((.)[1], 3)', 'varchar(max)') as systemname from dbo.tablename as T with (nolock)
- 先取表的第一行数据
top 1,把它转换成XML格式,存在T1.N里 - 用
CROSS APPLY把XML的每个列节点拆成行,每个行对应一个列的XML节点 - 给每个列名前面加
^^,然后用for xml path('')把所有带^^的列名拼接成一个XML字符串(比如^^id^^name^^age) - 最后用
.value('substring((.)[1], 3)', ...)去掉开头的两个^,得到id^^name^^age,作为结果的第一行(表头)
第二部分:生成每一行的列值
UNION ALL select ( select '^^'+ISNULL(T2.N.value('./text()[1]', 'varchar(max)'),'') from ( select T.* for xml path(''), type, ELEMENTS XSINIL ) as T1(N) cross apply T1.N.nodes('/*') as T2(N) for xml path(''), type, ELEMENTS XSINIL ).value('substring(./text()[1], 3)', 'varchar(max)') from dbo.tablename as T with (nolock)
- 对表的每一行数据,转换成XML格式
- 同样拆成每个列节点,然后取每个节点的文本值(
./text()[1]就是节点里的内容,也就是列值),如果为空就替换成空字符串,前面加^^ - 拼接成带
^^的字符串后,去掉开头的两个^,得到比如1^^张三^^25这样的行,和表头拼在一起输出。
内容的提问来源于stack exchange,提问作者Kath Chua




