繁体   English   中英

SQL Server从JOINed select语句生成XML数据行

[英]SQL Server to produce XML data rows from JOINed select statement

我在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>

有人知道该怎么做吗?

如果您想跳过所有详细信息,而只是看到一个答案,请查看此文章底部的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>
    ')

las,这行不通。 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'

请注意,我在提供的示例中更改了last属性的值,以包括一些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 &amp; &lt;&gt;!

这确保了属性值现在可以在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 &amp; &lt;&gt;!  1     2

现在,我们可以确定员工的第一个属性,因为up将是1 最后一个属性可以以类似的方式使用落笔列来识别。

有了所有这些,我们现在可以执行使用字符串连接构建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 &amp; &lt;&gt;!</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变量结尾。 您可以使用以下命令检查其格式是否正确:

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 &amp; &lt;&gt;!</attribute3></row></rows>

您可以接近-但您无法获得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标签名称。

这是一个答案,但是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')

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM