在SQL中引用XML类型时如何避免创建包装元素?

时间:2015-07-15 22:28:39

标签: sql-server xml for-xml

我在SQL Server 2008中使用FOR XML愚弄,看看它是否更适合构建Web服务响应而不是依赖于Hibernate&用于映射DTO的HQL(或从平面结果集中手动映射它们)。

我创建了一个虚构的例子,人们可能有孩子和一组电话号码。

我遇到SELECT name FROM personCte会产生不需要的包裹<name>元素的情况,导致<name><name first="test" last="test"/></name>

通过执行以下操作,我可以摆脱额外的包装元素,但我想知道是否有更合适的方法吗?

SELECT (select name)
FROM personCte

该解决方案的一个问题是它不能在CTE中使用,因为必须命名所有CTE列。

我还想知道是否有更好的方法可以将多个属性解压缩到单个元素(例如firstName和lastName到name)而不是执行子查询?

以下是我正在使用的示例代码:

DECLARE @Person TABLE (
    id int NOT NULL PRIMARY KEY IDENTITY(1, 1),
    firstName nvarchar(50) NOT NULL,
    lastName nvarchar(50) NOT NULL,
    parentId int NULL
);

DECLARE @PersonPhoneNumber TABLE (
    personId int NOT NULL,
    number char(12) NOT NULL
);


INSERT INTO @Person (firstName, lastName, parentId)
VALUES 
    ('Person', 'A', NULL),
    ('Person', 'B', 1),
    ('Person', 'C', 2);

INSERT INTO @PersonPhoneNumber
VALUES
    (1, '888-888-8888'),
    (1, '999-999-9999'),
    (3, '333-333-3333');



;WITH personCte AS (
    SELECT 
        id,
        (
            SELECT firstName AS [@first], lastName AS [@last]
            FROM @Person
            WHERE id = person.id
            FOR XML PATH('name'), TYPE
        ) AS name,
        (
            SELECT number
            FROM @PersonPhoneNumber
            WHERE personId = person.id
            FOR XML PATH(''), TYPE
        ) AS phoneNumbers,
        parentId
    FROM @Person person
)
SELECT 
        id, 
        (SELECT name), /* Used to avoid unwanted wrapping name element */
        phoneNumbers, 
        parentId,
        (
            SELECT id, (SELECT name), phoneNumbers, parentId
            FROM personCte person
            WHERE parentId = p.id
            FOR XML AUTO, TYPE
        ) AS children
    FROM personCte p
FOR XML AUTO, ROOT('persons'), TYPE

哪个正确产生:

<persons>
  <person id="1">
    <name first="Person" last="A" />
    <phoneNumbers>
      <number>888-888-8888</number>
      <number>999-999-9999</number>
    </phoneNumbers>
    <children>
      <person id="2" parentId="1">
        <name first="Person" last="B" />
      </person>
    </children>
  </person>
  <person id="2" parentId="1">
    <name first="Person" last="B" />
    <children>
      <person id="3" parentId="2">
        <name first="Person" last="C" />
        <phoneNumbers>
          <number>333-333-3333</number>
        </phoneNumbers>
      </person>
    </children>
  </person>
  <person id="3" parentId="2">
    <name first="Person" last="C" />
    <phoneNumbers>
      <number>333-333-3333</number>
    </phoneNumbers>
  </person>
</persons>

1 个答案:

答案 0 :(得分:1)

您可以使用query() XML方法排除不需要的嵌套:

select p.id, p.name.query('.')
FROM personCte p
FOR XML AUTO, ROOT('persons'), TYPE;

编辑:你需要的是用PATH()语法重写所有内容。在这种情况下,您不需要任何方法,并且您还可以指定嵌套节点,这些节点不能与AUTO一起使用。因此,您的完整查询将如下所示:

;WITH personCte AS (
    SELECT id, parentId, firstName, lastName, (
        SELECT number
        FROM @PersonPhoneNumber
        WHERE personId = person.id
        FOR XML PATH(''), TYPE
    ) AS phoneNumbers
    FROM @Person person
)
SELECT 
    p.id as [@id],
    p.parentId as [@parentid],
    p.firstName AS [name/@first],
    p.lastName AS [name/@last],
    p.phoneNumbers,
    (
        SELECT id as [@id], parentId as [@parentid],
            person.firstName AS [name/@first],
            person.lastName AS [name/@last],
            phoneNumbers
        FROM personCte person
        WHERE parentId = p.id
        FOR XML path('person'), TYPE
    ) AS children
FROM personCte p
FOR XML path('person'), ROOT('persons'), TYPE;