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