2011-08-02 57 views
1

我有這樣存儲在XML數據類型列的XML(將在表中的多個這樣的行) -SQLXML - 搜索和查詢節點元素?

<Root xmlns="http://tempuri.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 
    <Elem1 type="T1"> 
    <Name type="string" display="First name">John</Name> 
    <TimeZone display="Time zone"> 
     <DisplayName type="string" display="Display name">GMT Standard Time</DisplayName> 
    </TimeZone> 
    </Elem1> 
</Root> 

我如何進行篩選說(使用SQL Server 2008 R2)基於節點元素 - 獲得所有'Elem1'節點或獲取所有'Name'節點或獲得所有TimeZone節點?就像使用local-name()函數一樣?

編輯 - 部件解決方案 -

我得到了解決部分(見下文約翰的答覆,然後運行這個) -

SELECT C1.query('fn:local-name(.)') AS Nodes FROM [dbo].[MyXmlTable] AS MyXML CROSS APPLY MyXML.MyXmlCol.nodes('//*') AS T (C1) 

上面的查詢返回跨越所有節點元素表。現在,我想對特定元素進行過濾,並返回元素及其值或屬性值。如何實現這一點(通過使用WHERE子句或任何其他過濾機制)?

回答

1

我不確定你在找什麼結果f或者可能是這樣的事情。

declare @T table(XMLCol xml) 
insert into @T values 
('<Root xmlns="http://tempuri.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 
    <Elem1 type="T1"> 
    <Name type="string" display="First name">John</Name> 
    <TimeZone display="Time zone"> 
     <DisplayName type="string" display="Display name">GMT Standard Time</DisplayName> 
    </TimeZone> 
    </Elem1> 
</Root>') 

declare @Node varchar(50) 
set @Node = 'Elem1' 

select N.query('.') as Value 
from @T as T 
    cross apply T.XMLCol.nodes('//*[local-name()=sql:variable("@Node")]') as X(N) 

結果:

<p1:Elem1 xmlns:p1="http://tempuri.org" type="T1"> 
    <p1:Name type="string" display="First name">John</p1:Name> 
    <p1:TimeZone display="Time zone"> 
    <p1:DisplayName type="string" display="Display name">GMT Standard Time</p1:DisplayName> 
    </p1:TimeZone> 
</p1:Elem1> 

編輯

如果你想實際值,而不是整個XML,你可以像這樣來代替。

declare @Node varchar(50) 
set @Node = 'TimeZone' 

select N.value('.', 'varchar(100)') as Value 
from @T as T 
    cross apply T.XMLCol.nodes('//*[local-name()=sql:variable("@Node")]') as X(N) 

結果:

Value 
------------------ 
GMT Standard Time 
+0

結果應該只有值'GMT標準時間',輸入將是'TimeZone'節點元素 –

+0

@SeeSharp - 已更新的答案。如果你總是想查詢「TimeZone」,你只需用'TimeZone'替換'sql:variable(「@ Node」)''。 –

+0

謝謝Mikael! –

0

我不清楚你的輸出應該是什麼樣子。然而,這應該讓你開始:

create table MyXmlTable (MyXmlCol xml) 
insert into MyXmlTable (MyXmlCol) values 
(
' 
<Root xmlns="http://tempuri.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 
    <Elem1 type="T1"> 
    <Name type="string" display="First name">John</Name> 
    <TimeZone display="Time zone"> 
     <DisplayName type="string" display="Display name">GMT Standard Time</DisplayName> 
    </TimeZone> 
    </Elem1> 
    <Elem1 type="T2"> 
    <Name type="string" display="First name">Fred</Name> 
    <TimeZone display="Time zone"> 
     <DisplayName type="string" display="Display name">EST Standard Time</DisplayName> 
    </TimeZone> 
    </Elem1> 
</Root> 
'); 

;WITH XMLNAMESPACES(DEFAULT 'http://tempuri.org') 
select MyXmlCol.query('/Root/Elem1/Name') 
from MyXmlTable 

這將查詢「名稱」元素XML - 你可以修改這取決於你想要什麼樣的輸出的查詢。這是一個有點長,但SQLXML MSDN文章是相當翔實:

http://msdn.microsoft.com/en-us/library/ms345117(v=sql.90).aspx 

希望這有助於!

約翰

更新:你可以添加一個where子句這樣的東西。我還沒有你想要的輸出是什麼樣子清楚,但是這樣會過濾掉「Elem1」值:

SELECT C1.query('fn:local-name(.)') AS Nodes 
FROM [dbo].[MyXmlTable] AS MyXML 
CROSS APPLY MyXML.MyXmlCol.nodes('//*') AS T (C1) 
WHERE CAST(C1.query('fn:local-name(.)') AS NVARCHAR(32)) <> 'Elem1' 

還有一個更新;希望這是你正在尋找的答案!

嘗試在查詢中使用通配符。我不得不使用動態SQL,因爲XML查詢()函數只會爲路徑使用字符串文字(可以使用sql:variable(「@ filter」)作爲值,但我無法獲得路徑的效果。 )

DECLARE @filter nvarchar(20) 
SET @filter = '*/Elem1' 

DECLARE @sqlCommand nvarchar(1000) 
SET @sqlCommand = 
    ';WITH XMLNAMESPACES(DEFAULT ''http://tempuri.org'') 
    select MyXmlCol.query(''' + @filter + ''') 
    from MyXmlTable' 
print @sqlCommand 
EXECUTE sp_executesql @sqlCommand, N'@filter nvarchar(20)', @filter = @filter 

這將返回Elem1 XML(和所有子節點):

<p1:Elem1 xmlns:p1="http://tempuri.org" type="T1"> 
    <p1:Name type="string" display="First name">John</p1:Name> 
    <p1:TimeZone display="Time zone"> 
    <p1:DisplayName type="string" display="Display name">GMT Standard Time</p1:DisplayName> 
    </p1:TimeZone> 
</p1:Elem1> 
<p2:Elem1 xmlns:p2="http://tempuri.org" type="T2"> 
    <p2:Name type="string" display="First name">Fred</p2:Name> 
    <p2:TimeZone display="Time zone"> 
    <p2:DisplayName type="string" display="Display name">EST Standard Time</p2:DisplayName> 
    </p2:TimeZone> 
</p2:Elem1> 

如果你想挑選出 「時區」,你這樣做:

SET @filter = '*/*/TimeZone' 
+0

這很有用。謝謝。但是,用戶可以給任何節點元素作爲輸入變量,如'Elem1',並且基於此我必須過濾出該節點元素及其相應的值。我不知道Elem1或XML中的任何元素在哪裏。如果找不到節點元素,則返回錯誤。請注意:Elem1或Name元素可以成爲表中多行的XML列的一部分 –

+0

我看到了您的更新,我仍然有點不清楚您希望輸出的樣子。你能修改你的查詢來刪除某些結果嗎?讓我發佈我的答案更新。 – JohnD

+0

輸入將是任何節點元素,輸出應該是相應的節點元素及其值(全部在T-SQL中,因爲這應該作爲視圖的一部分存儲)。希望我清楚。 –

0

您可以將XML轉換爲表喜歡這裏:

declare @XML xml='<Root xmlns="http://tempuri.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 
    <Elem1 type="T1"> 
    <Name type="string" display="First name">John</Name> 
    <TimeZone display="Time zone"> 
     <DisplayName type="string" display="Display name">GMT Standard Time</DisplayName> 
    </TimeZone> 
    </Elem1> 
</Root> ' 

;WITH XMLNAMESPACES(DEFAULT 'http://tempuri.org'), 
numbers as(
SELECT ROW_NUMBER() OVER(ORDER BY o1.object_id,o2.object_id) Num 
FROM sys.objects o1 CROSS JOIN sys.objects o2), 
c as(
SELECT 
b.value('local-name(.)','nvarchar(1000)') Node_Name, 
b.value('./text()[1]','nvarchar(1000)') Node_Value, 
b.value('count(@*)','nvarchar(MAX)') AttributeCount, 
Num Attribute_Number 
FROM 
@xml.nodes('Root//*') a(b) 
CROSS APPLY Numbers 
WHERE Num<=b.value('count(@*)','nvarchar(MAX)') 
) 
SELECT c.Node_Name,c.node_Value,Attribute_Number, 
    @XML.query('for $Attr in //*/.[local-name(.)=sql:column("Node_Name")]/@*[sql:column("Attribute_Number")] return local-name($Attr)').value('.','nvarchar(MAX)') Attribute_Name, 
    @XML.value('data(//*/.[local-name(.)=sql:column("Node_Name")]/@*[sql:column("Attribute_Number")])[1]','nvarchar(1000)') Attribute_Value 
FROM c 

結果:

Node_Name node_Value   Attribute_Number Attribute_Name Attribute_Value 
Elem1  NULL      1    type    T1 
Name   John      1    type   string 
Name   John      2    display  First name 
TimeZone  NULL      1    display  Time zone 
DisplayName GMT Standard Time   1    type   string 
DisplayName GMT Standard Time   2    display  Display name 

以後可以曲這個結果可以獲得你需要的節點/屬性值。

但是它只適用於您的示例,當您只有一個節點並且所有名稱都是唯一的。在多節點XML中,您應該使用分層編號,如「1-1-2」或類似的東西。這更復雜,我不建議這樣去。