SQL Server从联接的select语句生成XML数据行
我在SQL Server 2008中有三个表,它们的设置如下: 员工表SQL Server从联接的select语句生成XML数据行,sql,sql-server,xml,path,Sql,Sql Server,Xml,Path,我在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 attributeid | attributeN
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
attributeid | attributeName
A1 | attribute1
A2 | attribute2
A3 | attribute3
连接到属性
dataId(PK) | empId(FK) | attributeid | attributeVal
10 | 1 | A1 | somevalue1
20 | 1 | A2 | somevalue2
30 | 2 | A1 | somevalue3
40 | 2 | A3 | somevalue4
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>
一些价值1
一些价值2
一些价值3
有价值的
有人知道如何做到这一点吗?你可以接近——但你不能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>
一些价值1
一些价值2
一些价值3
有价值的
您不能做的是使内部XML节点具有与属性名称匹配的标记名称-您必须使用一些固定标记名称(如我的示例中的
),然后将从表中检索到的值作为这些XML标记上的任一属性应用(如我的示例中的名称=
属性)或作为XML元素值
据我所知,没有办法使用
AttributeValue
作为XML标记名……这里有一个答案,但是PIVOT命令限制了您,因为您必须事先知道属性的名称。稍加调整,您可能可以动态地执行此操作(尝试在SQL Server 2005中搜索动态透视):
如果您想跳过所有血淋淋的细节,只想看到答案,请查看本文底部的SQL查询 这里的主要挑战是各种SQL Server FOR XML选项无法生成所需输出中规定的动态元素名称。因此,我的第一个答案是考虑简单地返回一个常规的SQL结果集,并让客户端生成XML。这是一个非常简单的流式转换。但是,这可能不是您的选择,因此我们继续让SQLServer生成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>
')
显然,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'
结果如下:
empId attributeName attributeVal
1 attribute1 someValue1
1 attribute2 someValue2
2 attribute1 someValue3
2 attribute3 someValue4 & <>!
结果如下:
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
我们现在可以识别员工的第一个属性,因为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
最后的XML如下所示:
<rows><row id="1"><attribute1>someValue1</attribute1><attribute2>someValue2</attribute2></row><row id="2"><attribute1>someValue3</attribute1><attribute3>someValue4 & <>!</attribute3></row></rows>
someValue1someValue2someValue3someValue4&!
如果您发布代码或XML,请在文本编辑器中突出显示这些行,并单击编辑器工具栏上的“代码”按钮(101 010),以精确地格式化和语法突出显示它!上面的xml应该是:somevalue1 somevalue2 somevalue3 somevalue4有人知道如何做到这一点吗?请看我的评论-编辑器中有一个“代码”按钮-使用它!这是可以做到的,但您需要动态SQL来做到这一点,而且它并不漂亮。
; 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>
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
SELECT CAST(@result AS XML)
<rows><row id="1"><attribute1>someValue1</attribute1><attribute2>someValue2</attribute2></row><row id="2"><attribute1>someValue3</attribute1><attribute3>someValue4 & <>!</attribute3></row></rows>