Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/sql/87.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
SQL Server从联接的select语句生成XML数据行_Sql_Sql Server_Xml_Path - Fatal编程技术网

SQL Server从联接的select语句生成XML数据行

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

我在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 | 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 &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。最后一个属性可以使用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 &amp; &lt;&gt;!</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 &amp; &lt;&gt;!</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 &amp; &lt;&gt;!</attribute3></row></rows>