在傳遞數據時,XML和JSON是最常用的數據格式,SQL Server從很早的版本就開始支持XML格式,而對於JSON格式,SQL Server從2016版本開始支持。大多數資料庫系統並沒有升級到SQL Server 2016版本,因此在傳遞格式化的數據時,通常還是使用XML格式。對我而言,查詢和解 ...
在傳遞數據時,XML和JSON是最常用的數據格式,SQL Server從很早的版本就開始支持XML格式,而對於JSON格式,SQL Server從2016版本開始支持。大多數資料庫系統並沒有升級到SQL Server 2016版本,因此在傳遞格式化的數據時,通常還是使用XML格式。對我而言,查詢和解析XML格式的數據需要掌握的知識點較多,MSDN上關於XML的文檔,又試圖把XML的各個方面都講解地清清楚楚,以至於內容冗雜,使學習過程變得困難。我十分不喜歡學習這些不常用的數據結構,再說,在平時的資料庫開發中,用到XML的地方也很少,可是,一旦在應用程式中用到XML,就只有頭疼的份了,既然避不開XML,那就用最簡單的方法學習它,瞭解它,使用它,以備不時之需。寫這篇文章,就是以最簡單的方式,分享XML最常用的使用方法。
一,XML數據格式的簡單介紹
1,最簡單的XML格式
XML數據最簡單的格式是:
- 開始標簽:<tag>
- 標簽的屬性,屬性值用雙引號:<tag id="1" name="azure">,在單個節點中,屬性名不能重覆,屬性之間使用空格分隔,在開始標簽中,才能設置屬性;
- 結束標簽:</tag>,結束標簽不能有屬性;
- 子節點:在開始標簽和結束標簽,可以包含節點,叫做子節點;
- 節點值:在開始標簽和結束標簽的標量值,叫做節點值;
2,使用字元串對XML數據賦值
數據類型XML用於存儲XML格式化的文本數據,在本例中,聲明一個XML類型的變數 @xml,並賦值,後文示例都使用該變數用於數據查詢。
declare @xml xml set @xml=' <Expression ID="1" TaxonomyID="1"> <SubExpression ID="1" OperandType="Tag" Operator="and"> <Oprand ID="268819" Name="abuse" /> <Oprand ID="277029" Name="mongohq" /> <Oprand ID="516813" Name="access" /> </SubExpression> <SubExpression ID="2" OperandType="Tag" Operator="and"> <Oprand ID="283839" Name="reviews" /> <Oprand ID="697348" Name="retention" /> </SubExpression> <SubExpression ID="3" OperandType="Tag" Operator="not"> <Oprand ID="281556" Name="richfaces" /> <Oprand ID="2993766" Name="rgp" /> </SubExpression> </Expression>'
二,XPath路徑表達式
XPath 使用路徑表達式在 XML 文檔中選取節點,節點是通過沿著路徑選取的,XPath是查詢XML數據時必備的參數。
常用的路徑表達式是:
- . :選取當前節點;
- .. :選取當前節點的父節點;
- / :從根節點開始;
- // :從匹配選擇的節點開始選取,而不考慮其位置;
- * :通配符,匹配任意字元,或任意節點;
- node() :匹配任意節點,跟通配符 * 功能相似;
- @PropertyName :選取屬性;
在路徑表達式中,跟節點的選取有關的表達式是:
- NodeName:選取指定節點名及其所有子節點;
- NodeName[N]:選取指定節點集合的第N個節點;
- NodeName[@Name]:選取當前節點中帶有指定屬性的節點;
三,XML數據的查詢(query()函數)
@xml.query(’xpath‘)函數,參數是路徑表達式,返回XML數據類型的結果,該XML是非類型化(untyped)的。
1,選取節點及其子節點
示例中,從根節點Expression開始,選取SubExpression節點及其子節點:
select @xml.query('/Expression/SubExpression')
query()函數返回的結果如下,該查詢結果是非類型化的XML數據。
<SubExpression ID="1" OperandType="Tag" Operator="and"> <Oprand ID="268819" Name="abuse" /> <Oprand ID="277029" Name="mongohq" /> <Oprand ID="516813" Name="access" /> </SubExpression> <SubExpression ID="2" OperandType="Tag" Operator="and"> <Oprand ID="283839" Name="reviews" /> <Oprand ID="697348" Name="retention" /> </SubExpression> <SubExpression ID="3" OperandType="Tag" Operator="not"> <Oprand ID="281556" Name="richfaces" /> <Oprand ID="2993766" Name="rgp" /> </SubExpression>
2,選取指定節點的所有子節點集合
select @xml.query('/Expression/SubExpression/node()') select @xml.query('/Expression/SubExpression/*')
結果集是SubExpression節點下的所有子節點:
<Oprand ID="268819" Name="abuse" /> <Oprand ID="277029" Name="mongohq" /> <Oprand ID="516813" Name="access" /> <Oprand ID="283839" Name="reviews" /> <Oprand ID="697348" Name="retention" /> <Oprand ID="281556" Name="richfaces" /> <Oprand ID="2993766" Name="rgp" />
四,XML數據的查詢(value()函數)
@xml.value('xpath','sql_data_type'),返回XML數據中單個屬性的標量值,在使用value()函數時,xpath 參數必須指定返回的是單個值,而value()函數不會去check返回值的數量。
一般情況下,即使xml數據只有一個屬性值,靜態類型化(Static typing)要求,xpath表達式也必須顯式指定返回單個標量值,因此,必須指定在xpath函數的末尾添加”[1]“,通常的xpath表達式是”(xpath)[1]“。
select @xml.value('(/Expression/SubExpression[1]/@ID)[1]','int') select @xml.value('(/Expression/SubExpression/@ID)[1]','int')
五,XML數據的查詢(nodes()函數)
@xml.nodes ('xpath') 函數返回節點的集合,用於把XML數據轉換為關係數據表,返回的每一個行都是XML數據類型,語法是:
nodes ('xpath') as table(column)
通過nodes()函數,返回SubExpression節點及其屬性,由於單個節點中,屬性名不可能重覆,因此,在nodes()函數返回的單個節點中,不需要通過xpath路由,直接獲取當前節點的屬性值,這樣,可以在xpath表達式中直接指定屬性,不需要顯式以“[1]”結尾。
示例代碼如下,在value()函數中,直接指定屬性值,表示獲取當前節點的屬性值:
select t.v.query('.') as SubExpression ,t.v.value('@ID','int') as SubExpressionID ,t.v.value('@OperandType','varchar(16)') as OperandType ,t.v.value('@Operator','varchar(16)') as Operator from @xml.nodes('/Expression/SubExpression') as t(v)
通過cross apply 連接操作,把SubExpression節點下的所有數據都轉換為關係型數據,並把該數據存儲到臨時數據表#Expressions中:

;with cte_Expressions as ( select e.v.query('.') as Expression ,e.v.value('@ID','int') as ExpressionID ,e.v.value('@TaxonomyID','int') as TaxonomyID from @xml.nodes('/Expression') as e(v) ) ,cte_SubExpression as ( select e.ExpressionID ,e.TaxonomyID ,se.SubExpression ,se.SubExpressionID ,se.OperandType ,se.Operator from cte_Expressions e cross apply ( select t.v.query('.') as SubExpression ,t.v.value('@ID','int') as SubExpressionID ,t.v.value('@OperandType','varchar(16)') as OperandType ,t.v.value('@Operator','varchar(16)') as Operator from e.Expression.nodes('/Expression/SubExpression') as t(v) ) as se ) select p.TaxonomyID ,p.ExpressionID ,p.SubExpressionID ,p.OperandType ,p.Operator ,d.OperandID ,d.OperandName from cte_SubExpression p cross apply ( select t.v.value('@ID','int') as OperandID ,t.v.value('@Name','varchar(32)') as OperandName from p.SubExpression.nodes('/SubExpression/Oprand') as t(v) ) as dView Code
六,把行集數據轉化為XML數據(for xml path)
把行集數據轉化為XML數據,需要用到for xml path子句,該子句的特點是:
- path('root') 子句用於指定根節點;
- select子句的欄位別名用於指定屬性,別名中必須使用@符號標識出屬性名,例如:'@PropertyName';
- 在select 子句中,如果不在別名中把欄位指定為屬性,那麼該欄位的值作為節點值,節點值分為標量類型和XML類型;
- 對於標量類型,節點值是標量值;
- 對於XML類型,節點值是子節點的集合;
例如,要把數據轉換為如下的關係型數據結構,其SubExpression欄位是一個非類型化的XML數據,要完成這樣的數據轉換,必須使用for xml path子句和cast()類型轉換:
<SubExpression ID="1" OperandType="Tag" Operator="not"> <Oprand ID="268819" Name="abuse" /> <Oprand ID="277029" Name="mongohq" /> <Oprand ID="516813" Name="access" /> </SubExpression> <SubExpression ID="2" OperandType="Tag" Operator="not"> <Oprand ID="283839" Name="reviews" /> <Oprand ID="697348" Name="retention" /> </SubExpression> <SubExpression ID="3" OperandType="Tag" Operator="not"> <Oprand ID="281556" Name="richfaces" /> <Oprand ID="2993766" Name="rgp" /> </SubExpression>
使用類型轉換的目的,是為了把for xml path返回的字元串轉換成XML數據類型,這樣,就能以XML格式嵌入到上次的for xml path的結構中,作為子節點:

;with cte_Expressions as
(
select distinct ExpressionID
,TaxonomyID
from #Expressions with(nolock)
)
,cte_SubExpressions as
(
select o.ExpressionID
,o.SubExpressionID
,o.OperandType
,o.Operator
from #Expressions o with(nolock)
group by o.ExpressionID
,o.SubExpressionID
,operandType
,o.Operator
)
select e.TaxonomyID as TaxonomyID
,e.ExpressionID as ExpressionID
,cast(
(
select o.SubExpressionID as '@ID'
,o.OperandType as '@OperandType'
,case o.Operator when '&' then 'and' else 'not' end as '@Operator'
,cast((
select op.OperandID as '@ID'
,op.OperandName as '@Name'
from #Expressions op with(nolock)
where o.ExpressionID=op.ExpressionID
and o.SubExpressionID=op.SubExpressionID
for xml path('Oprand')
)as xml)
from cte_SubExpressions o
where o.ExpressionID=e.ExpressionID
for xml path('SubExpression')
) as xml)
as SubExpressions
from cte_Expressions e
View Code
還有兩個函數modify()和exist(),用於XML數據的修改和檢查,由於在我當前接觸的項目中,沒有用到過,我就不寫了。
到此,文章也該結尾了,XML的極簡用法已經總結了很多,在以後工作中國,如果用到XML時,翻開這篇文章,能夠快速解決XML常見的數據查詢和解析問題,這樣就足夠了。
參考文檔: