如果你想跳過所有的血淋淋的細節,只是看到一個答案,查看SQL查詢在這篇文章的底部。
這裏的主要挑戰是各種SQL Server選項無法生成所需輸出中規定的動態元素名稱。因此,我的第一個答案是考慮簡單地返回一個常規的SQL結果集並讓客戶端生成XML。這是一個非常簡單的流媒體轉換。但是,這可能不適合您,所以我們繼續讓SQL Server生成XML。
我的第二個想法是使用SQL Server的內置的XQuery功能來進行轉換,這樣的:
/* WARNING: the following SQL does not work */
SELECT
CAST((SELECT * FROM data FOR XML RAW) AS XML)
.query('
<rows>
{
for $empId in distinct-values(/row/@empId)
return
<row empid="{$empId}">
{
for $attr in /row[@empId = $empId]
return
attribute { "attribute" } { $attr/@attributeValue }
}
</row>
}
</rows>
')
唉,這是行不通的。 SQL Server的抱怨:
Msg 9315, Level 16, State 1, Line 25
XQuery [query()]: Only constant expressions are supported for the name expression
of computed element and attribute constructors.
顯然,XQuery實現從相同的限制受到的FOR XML功能。所以,我的第二個回答是建議在客戶端生成XML :)但是,如果你堅持從SQL生成XML,然後繫好安全帶...
總體戰略是放棄SQL Server的SQL生成的本地設施。相反,我們將使用字符串連接來構建XML文檔。如果這種做法是進攻,你現在可以停止閱讀:)
讓我們先從產生的樣本數據集一起玩:在所提供的例子
SELECT NULL AS empId INTO employee WHERE 1=0
UNION SELECT 1
UNION SELECT 2
SELECT NULL AS dataId, NULL AS empId, NULL AS attributeId, NULL AS attributeVal INTO employeeAttributes WHERE 1=0
UNION SELECT 10, 1, 'A1', 'someValue1'
UNION SELECT 20, 1, 'A2', 'someValue2'
UNION SELECT 30, 2, 'A1', 'someValue3'
UNION SELECT 40, 2, 'A3', 'someValue4 & <>!'
SELECT NULL AS attributeId, NULL AS attributeName INTO attributes WHERE 1=0
UNION SELECT 'A1', 'attribute1'
UNION SELECT 'A2', 'attribute2'
UNION SELECT 'A3', 'attribute3'
請注意,我已經改變了最後一個屬性的值包括一些XML不友好的字符。
現在,把一個基本的SQL查詢來執行必要的連接:
SELECT
e.empId
, a.attributeName
, ea.attributeVal
FROM employee AS e
INNER JOIN employeeAttributes AS ea
ON ea.empId = e.empId
INNER JOIN attributes AS a
ON a.attributeId = ea.attributeId
這給出了這樣的結果:
empId attributeName attributeVal
1 attribute1 someValue1
1 attribute2 someValue2
2 attribute1 someValue3
2 attribute3 someValue4 & <>!
在最後一個屬性的那些奇怪的字符,將會給我們帶來麻煩。讓我們改變查詢來轉義它們。
; WITH
cruftyData AS (
SELECT
e.empId
, a.attributeName
, (SELECT ea.attributeVal AS x FOR XML RAW) AS attributeValXml
FROM employee AS e
INNER JOIN employeeAttributes AS ea
ON ea.empId = e.empId
INNER JOIN attributes AS a
ON a.attributeId = ea.attributeId
)
, data AS (
SELECT
empId
, attributeName
, SUBSTRING(attributeValXml, 9, LEN(attributeValXml)-11) AS attributeVal
FROM cruftyData
)
SELECT * FROM data
與結果:
empId attributeName attributeValXml
1 attribute1 someValue1
1 attribute2 someValue2
2 attribute1 someValue3
2 attribute3 someValue4 & <>!
這確保了屬性值現在可以在XML文檔中安全地使用。那屬性名稱呢? XML屬性名稱的規則比元素內容的規則更嚴格。我們將假定屬性名稱是有效的XML標識符。如果不是這樣,那麼需要設計一些方案將數據庫中的名稱轉換爲有效的XML名稱。這是作爲練習留給讀者:)
下一個挑戰是確保屬性爲每個員工分組在一起,並且我們可以知道我們何時處於組中的第一個或最後一個值。以下是更新的查詢:
; WITH
cruftyData AS (
SELECT
e.empId
, a.attributeName
, (SELECT ea.attributeVal AS x FOR XML RAW) AS attributeValXml
FROM employee AS e
INNER JOIN employeeAttributes AS ea
ON ea.empId = e.empId
INNER JOIN attributes AS a
ON a.attributeId = ea.attributeId
)
, data AS (
SELECT
empId
, attributeName
, SUBSTRING(attributeValXml, 9, LEN(attributeValXml)-11) AS attributeVal
, ROW_NUMBER() OVER (PARTITION BY empId ORDER BY attributeName DESC) AS down
, ROW_NUMBER() OVER (PARTITION BY empId ORDER BY attributeName) AS up
FROM cruftyData
)
SELECT * FROM data ORDER BY 1, 2
唯一的變化是將下和了列添加到結果集:
empId attributeName attributeVal down up
1 attribute1 someValue1 2 1
1 attribute2 someValue2 1 2
2 attribute1 someValue3 2 1
2 attribute3 someValue4 & <>! 1 2
我們現在能夠確定的第一個屬性爲僱員因爲以上將是。最後的屬性可以使用下的列以類似的方式識別。
有了這一切,我們現在可以執行使用字符串連接來構建XML結果的討厭業務了。
; WITH
cruftyData AS (
SELECT
e.empId
, a.attributeName
, (SELECT ea.attributeVal AS x FOR XML RAW) AS attributeValXml
FROM employee AS e
INNER JOIN employeeAttributes AS ea
ON ea.empId = e.empId
INNER JOIN attributes AS a
ON a.attributeId = ea.attributeId
)
, data AS (
SELECT
empId
, attributeName
, SUBSTRING(attributeValXml, 9, LEN(attributeValXml)-11) AS attributeVal
, ROW_NUMBER() OVER (PARTITION BY empId ORDER BY attributeName DESC) AS down
, ROW_NUMBER() OVER (PARTITION BY empId ORDER BY attributeName) AS up
FROM cruftyData
)
, xmlData AS (
SELECT
empId
, up
, CASE WHEN up <> 1 THEN '' ELSE '<row id="' + CAST (empId AS NVARCHAR) + '">' END AS xml1
, '<' + attributeName + '>' + attributeVal + '</' + attributeName + '>' AS xml2
, CASE WHEN down <> 1 THEN '' ELSE '</row>' END AS xml3
FROM data
)
SELECT xml1, xml2, xml3
--SELECT @result = @result + 'wombat' + xmlString
FROM xmlData
ORDER BY empId, up
與結果:
xml1 xml2 xml3
<row id="1"> <attribute1>someValue1</attribute1>
<attribute2>someValue2</attribute2> </row>
<row id="2"> <attribute1>someValue3</attribute1>
<attribute3>someValue4 & <>!</attribute3> </row>
剩下的是連接所有的行在一起,並添加根行標籤。由於T-SQL沒有(還)有字符串連接集合函數,因此我們將使用一個變量作爲累加器。這裏是最終的查詢,在其所有的榮耀哈克:
DECLARE @result AS NVARCHAR(MAX)
SELECT @result = '<rows>'
; WITH
cruftyData AS (
SELECT
e.empId
, a.attributeName
, (SELECT ea.attributeVal AS x FOR XML RAW) AS attributeValXml
FROM employee AS e
INNER JOIN employeeAttributes AS ea
ON ea.empId = e.empId
INNER JOIN attributes AS a
ON a.attributeId = ea.attributeId
)
, data AS (
SELECT
empId
, attributeName
, SUBSTRING(attributeValXml, 9, LEN(attributeValXml)-11) AS attributeVal
, ROW_NUMBER() OVER (PARTITION BY empId ORDER BY attributeName DESC) AS down
, ROW_NUMBER() OVER (PARTITION BY empId ORDER BY attributeName) AS up
FROM cruftyData
)
, xmlData AS (
SELECT
empId
, up
, CASE WHEN up <> 1 THEN '' ELSE '<row id="' + CAST (empId AS NVARCHAR) + '">' END AS xml1
, '<' + attributeName + '>' + attributeVal + '</' + attributeName + '>' AS xml2
, CASE WHEN down <> 1 THEN '' ELSE '</row>' END AS xml3
FROM data
)
SELECT @result = @result + xml1 + xml2 + xml3
FROM xmlData
ORDER BY empId, up
SELECT @result = @result + '</rows>'
SELECT @result
的XML在@result變量結束。您可以檢查它是否使用格式良好的XML:
SELECT CAST(@result AS XML)
最終的XML看起來是這樣的:
<rows><row id="1"><attribute1>someValue1</attribute1><attribute2>someValue2</attribute2></row><row id="2"><attribute1>someValue3</attribute1><attribute3>someValue4 & <>!</attribute3></row></rows>
如果您發佈的代碼或XML,** **請突出顯示文本的那些行編輯器,然後單擊編輯器工具欄上的「代碼」按鈕(101 010)以良好地格式化和語法突出顯示它! – 2010-12-05 20:54:31
xml以上應爲:
任何人都知道如何做到這一點? – Puc 2010-12-05 20:56:42請看我的評論 - 編輯器中有一個「代碼」按鈕 - 使用它! – 2010-12-05 21:04:47