我在SQL Server 2008中有三个表,其设置如下:
员工表
empid(PK)
1
2
加入了EMPLOYEEATTRIBUTES
dataId(PK) | empId(FK) | attributeid | attributeVal
10 | 1 | A1 | somevalue1
20 | 1 | A2 | somevalue2
30 | 2 | A1 | somevalue3
40 | 2 | A3 | somevalue4
加入了ATTRIBUTES
attributeid | attributeName
A1 | attribute1
A2 | attribute2
A3 | attribute3
我需要将xml数据输出为以下格式
<rows>
<row empid="1">
<attribute1>somevalue1</attribute1>
<attribute2>somevalue2</attribute1>
</row>
<row empid="2">
<attribute1>somevalue3</attribute1>
<attribute3>somevalue4</attribute1>
</row>
</rows>
任何人都知道如何做到这一点??
答案 0 :(得分:2)
如果您想跳过所有血腥细节并只看到答案,请查看此帖子底部的SQL查询。
这里的主要挑战是各种SQL Server FOR XML 选项无法生成所需输出中规定的动态元素名称。因此,我的第一个答案是考虑简单地返回传统的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
唯一的变化是将 down 和 up 列添加到结果集中:
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
我们现在可以识别员工的第一个属性,因为 up 将 1 。可以使用 down 列以类似的方式识别最后一个属性。
有了这一切,我们现在有能力执行使用字符串连接构建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(尚未)具有字符串连接聚合函数,因此我们将使用变量作为累加器。 这是最后的查询,其所有的hacky荣耀:
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 变量中。您可以使用以下方法检查它是否格式正确:
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>
答案 1 :(得分:1)
你可以近距离接触 - 但你不能100%得到你想要的输出。
使用此查询:
SELECT
EmpID AS '@empid',
(
SELECT
a.AttributeName AS '@name',
ea.AttributeVal
FROM dbo.EmployeeAttributes ea
INNER JOIN dbo.Attributes a ON ea.AttributeId = a.AttributeId
WHERE ea.EmpID = e.EmpID
FOR XML PATH ('attribute'), TYPE
)
FROM dbo.Employee e
FOR XML PATH('row'), ROOT('rows')
你得到这个输出:
<rows>
<row empid="1">
<attribute name="Attribute1">
<AttributeVal>SomeValue1</AttributeVal>
</attribute>
<attribute name="attribute2">
<AttributeVal>SomeValue2</AttributeVal>
</attribute>
</row>
<row empid="2">
<attribute name="Attribute1">
<AttributeVal>SomeValue3</AttributeVal>
</attribute>
<attribute name="attribute3">
<AttributeVal>SomeValue4</AttributeVal>
</attribute>
</row>
</rows>
您不能做的是让内部XML节点具有与属性名称匹配的标记名称 - 您必须使用一些固定标记名称(如我的示例中的<attribute>
),以及然后将从表中检索到的值作为这些XML标记的属性(如我的示例中的name=
属性)或XML元素值应用。
据我所知,无法使用AttributeValue
作为XML标记名称....
答案 2 :(得分:0)
这是一个答案,但PIVOT命令限制了您必须提前知道属性的名称。稍微调整一下,您可以动态地执行此操作(尝试在SQL Server 2005中搜索动态数据透视):
DECLARE @Employee TABLE ( empid INT )
DECLARE @EA TABLE
(
dataid INT
, empid INT
, attributeid CHAR(2)
, AttributeVal VARCHAR(100)
)
DECLARE @Attributes TABLE
(
AttributeID CHAR(2)
, AttributeName VARCHAR(100)
)
INSERT INTO @Employee
VALUES ( 1 ),
( 2 )
INSERT INTO @EA
( dataid, empid, attributeid, AttributeVal )
VALUES ( 10, 1, 'A1', 'somevalue1' )
, ( 20, 1, 'A2', 'somevalue2' )
, ( 30, 2, 'A1', 'somevalue3' )
, ( 40, 2, 'A3', 'somevalue4' )
INSERT INTO @Attributes
( AttributeID, AttributeName )
VALUES ( 'A1', 'attribute1' )
,
( 'A2', 'attribute2' )
,
( 'A3', 'attribute3' )
SELECT empID as '@empid'
, attribute1
, attribute2
, attribute3
, attribute4
FROM ( SELECT e.empid
, a.AttributeName
, ea.AttributeVal
FROM @Employee e
JOIN @EA ea ON e.empid = ea.empid
JOIN @Attributes a ON ea.attributeid = a.attributeid
) ps PIVOT
( MIN(AttributeVal) FOR AttributeName IN ( [attribute1], [attribute2], [attribute3], [attribute4] ) ) AS pvt
FOR XML PATH('row'), ROOT('rows')