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

How to avoid creating a wrapping element when referencing an XML type in SQL?

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

我创建了一个虚构的示例,其中人们可能拥有 children 和一组 phone 个数字。

我遇到的情况是 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>

您可以使用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;