2015-07-15 3 views
0

Я обманываю с FOR XML в SQL Server 2008, чтобы узнать, будет ли он лучше подходит для создания ответа веб-службы, а не полагаться на Hibernate & HQL для отображения DTO (или сопоставления их вручную из плоских результирующих наборов).Как избежать создания элемента переноса при ссылке на тип XML в SQL?

Я создал фиктивный пример, где люди могут иметь детей и набор телефонных номеров.

Я столкнулся с ситуацией, когда SELECT name FROM personCte будет выдавать ненужную обертку <name>, что приводит к <name><name first="test" last="test"/></name>.

Я могу избавиться от дополнительного элемента упаковки, выполнив следующее, но мне интересно, есть ли более подходящий способ сделать это?

SELECT (select name) 
FROM personCte 

Одна проблема с этим решением является то, что он не может быть использован в КТР, поскольку все столбцы CTE должны быть названы.

Я также хотел бы знать, есть ли лучшие способы unflatten нескольких свойств в один элемент (например, firstName и lastName в имя), чем выполнение подзапроса?

Вот пример кода, я использую:

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

Вы можете использовать метод query() XML, чтобы исключить нежелательную вложенность:

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

EDIT: вам нужно переписать все с помощью PATH() syn налог. В этом случае вам не нужны никакие методы, а также вы сможете указать вложенные узлы, которые не работают с 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; 
+0

Является ли 'запрос' дорогостоящим вызовом с точки зрения производительности? По unflattening я имею в виду, что в базе данных у меня есть столбец firstName и lastName, но в фактическом XML я хочу элемент ''. В настоящее время я использую вложенный запрос для достижения этого, но, возможно, есть лучший способ? – plalx

+0

Производительность 'query()' будет арахисами по сравнению с 'for xml auto'. Вы действительно должны избавиться от этого монстра и переписать все с помощью синтаксиса 'path()'. –

+0

Кроме того, с помощью 'path()' вы можете определить более сложную структуру, иногда не прибегая к дополнительным подзапросам. В этом случае это будет нечто вроде: 'select p.id as [@Id], p.parentId как [@ParentId], p.firstName как [Name/@ First], p.lastName как [Name/@ Last ] от @Person p для xml path ('Person'), type, root ('Person'); ' –