Как вещи и «для пути XML» работают в Sql Server

367

Таблица является:

+----+------+
| Id | Name |
+----+------+    
| 1  | aaa  |
| 1  | bbb  |
| 1  | ccc  |
| 1  | ddd  |
| 1  | eee  |
+----+------+

Требуемый выход:

+----+---------------------+
| Id |        abc          |
+----+---------------------+ 
|  1 | aaa,bbb,ccc,ddd,eee |
+----+---------------------+

Запрос:

SELECT ID, 
    abc = STUFF(
                 (SELECT ',' + name FROM temp1 FOR XML PATH ('')), 1, 1, ''
               ) 
FROM temp1 GROUP BY id

Этот запрос работает правильно. Но мне просто нужно объяснение, как это работает, или есть какой-то другой или короткий способ сделать это.

Я очень смущен, чтобы понять это.

Пунит Чавла
источник
1
См. Также stackoverflow.com/questions/21623593/…
ChrisF
1
Я сделал для этого страницу SqlFiddle , чтобы увидеть, как она работает в реальной жизни. Надеюсь, что это помогает другим.
Сабунку
1
^ Возможно, IDон уникален в другой таблице разных сущностей, и эта таблица хранит вещи, которые принадлежат им.
Ник Роландо
Этот запрос не работает, если некоторые строки имеют другой идентификатор. например, если у «ddd» и «eee» есть Id 2.
KevinVictor
10
Время моего ежемесячного посещения этой страницы, чтобы увидеть, где я ошибся.
Тейлор Экли

Ответы:

683

Вот как это работает:

1. Получить строку XML-элемента с FOR XML

Добавление FOR XML PATH в конец запроса позволяет выводить результаты запроса в виде элементов XML с именем элемента, содержащимся в аргументе PATH. Например, если мы запустим следующий оператор:

SELECT ',' + name 
              FROM temp1
              FOR XML PATH ('')

Передавая пустую строку (FOR XML PATH ('')), мы получаем следующее:

,aaa,bbb,ccc,ddd,eee

2. Удалите начальную запятую с помощью STUFF

Оператор STUFF буквально «вставляет» одну строку в другую, заменяя символы в первой строке. Однако мы используем его просто для удаления первого символа из результирующего списка значений.

SELECT abc = STUFF((
            SELECT ',' + NAME
            FROM temp1
            FOR XML PATH('')
            ), 1, 1, '')
FROM temp1

Параметры STUFF:

  • Строка, которая будет «заполнена» (в нашем случае полный список имен с запятой)
  • Место для начала удаления и вставки символов (1, мы вставляем пустую строку)
  • Количество символов для удаления (1, являясь ведущей запятой)

Итак, мы заканчиваем с:

aaa,bbb,ccc,ddd,eee

3. Присоединяйтесь по id, чтобы получить полный список

Затем мы просто присоединяем это к списку идентификаторов во временной таблице, чтобы получить список идентификаторов с именем:

SELECT ID,  abc = STUFF(
             (SELECT ',' + name 
              FROM temp1 t1
              WHERE t1.id = t2.id
              FOR XML PATH (''))
             , 1, 1, '') from temp1 t2
group by id;

И у нас есть наш результат:

-----------------------------------
| Id        | Name                |
|---------------------------------|
| 1         | aaa,bbb,ccc,ddd,eee |
-----------------------------------

Надеюсь это поможет!

FutbolFan
источник
57
Вы должны работать в команде Microsoft по документации (если есть)
Fandango68
55
@ Fandango68, @ FutbolFan - Он не может работать на команду документации Microsoft. Его объяснения слишком четкие и слишком прямые. ;-)
Крис
1
@ChrisProsser Я согласен. Oracle опередил Microsoft в этом, введя LISTAGGфункцию в Oracle 11gR2. Я скучаю по этой функциональности в те дни, когда я должен использовать это вместо. techonthenet.com/oracle/functions/listagg.php
FutbolFan
2
Привет. На шаге 1, если вы сделаете: ВЫБЕРИТЕ имя ИЗ Temp1 FOR XML PATH ('') ... вы получите <name> aaa </ name> <name> bbb </ name> ... и т.д. ... я не сделал ' Поначалу это не осознается ... Изменение его на SELECT '' + name ... etc ... удаляет теги.
КевинВиктор
1
@ChrisProsser - Sybase ASA listфункционирует десятилетиями. К сожалению, Microsoft основала SQLServer на Sybase ASE и никогда не беспокоилась о функции списка до прошлого года. Я согласен - это ошеломляет. И тогда они делают, они называют это string_agg. Я бы подумал, что listэто довольно очевидно.
youcantryreachingme
76

В этой статье рассматриваются различные способы объединения строк в SQL, в том числе улучшенная версия кода, которая не кодирует XML-данные сцепленных значений.

SELECT ID, abc = STUFF
(
    (
        SELECT ',' + name
        FROM temp1 As T2
        -- You only want to combine rows for a single ID here:
        WHERE T2.ID = T1.ID
        ORDER BY name
        FOR XML PATH (''), TYPE
    ).value('.', 'varchar(max)')
, 1, 1, '')
FROM temp1 As T1
GROUP BY id

Чтобы понять, что происходит, начните с внутреннего запроса:

SELECT ',' + name
FROM temp1 As T2
WHERE T2.ID = 42 -- Pick a random ID from the table
ORDER BY name
FOR XML PATH (''), TYPE

Поскольку вы указываете FOR XML, вы получите одну строку, содержащую фрагмент XML, представляющий все строки.

Поскольку вы не указали псевдоним столбца для первого столбца, каждая строка будет заключена в элемент XML с именем, указанным в скобках после FOR XML PATH. Например, если бы вы имели FOR XML PATH ('X'), вы получите XML-документ, который выглядел бы так:

<X>,aaa</X>
<X>,bbb</X>
...

Но, так как вы не указали имя элемента, вы просто получаете список значений:

,aaa,bbb,...

Он .value('.', 'varchar(max)')просто извлекает значение из полученного фрагмента XML, без кодирования XML каких-либо «специальных» символов. Теперь у вас есть строка, которая выглядит следующим образом:

',aaa,bbb,...'

Затем STUFFфункция удаляет начальную запятую, давая вам окончательный результат, который выглядит следующим образом:

'aaa,bbb,...'

На первый взгляд это выглядит довольно запутанно, но, как и в некоторых других вариантах, имеет тенденцию работать довольно хорошо.

Ричард Деминг
источник
2
Какая польза от Type в вашем запросе. Я думаю, что для определения, результат пути XML будет сохранен в значении (не уверен, объясните это если неправильно).
Puneet Chawla
8
@PuneetChawla: директива указывает SQL для возврата данных с использованием типа. Без этого данные возвращаются как . Это используется здесь, чтобы избежать проблем с XML-кодированием, если в столбце есть специальные символы . TYPExmlnvarchar(max)name
Ричард Деминг,
2
@barlop: Как объясняется в статье о SimpleTalk , если вы отбросите TYPEи .value('.', 'varchar(max)'), то в результате вы можете получить закодированные в формате XML объекты.
Ричард Деминг,
1
@RichardDeeming Вы имеете в виду, если данные содержат или могут содержать угловые скобки?
Бароп
1
Но, так как вы не указали имя элемента, вы просто получаете список значений , это понимание, которое я пропустил. Спасибо.
Адам
44

Режим PATH используется при генерации XML из запроса SELECT

1. SELECT   
       ID,  
       Name  
FROM temp1
FOR XML PATH;  

Ouput:
<row>
<ID>1</ID>
<Name>aaa</Name>
</row>

<row>
<ID>1</ID>
<Name>bbb</Name>
</row>

<row>
<ID>1</ID>
<Name>ccc</Name>
</row>

<row>
<ID>1</ID>
<Name>ddd</Name>
</row>

<row>
<ID>1</ID>
<Name>eee</Name>
</row>

Выходные данные - это элементно-ориентированный XML, где каждое значение столбца в результирующем наборе строк заключено в элемент строки. Поскольку в предложении SELECT не указываются псевдонимы для имен столбцов, сгенерированные имена дочерних элементов совпадают с именами соответствующих столбцов в предложении SELECT.

Для каждой строки в наборе строк добавляется тег.

2.
SELECT   
       ID,  
       Name  
FROM temp1
FOR XML PATH('');

Ouput:
<ID>1</ID>
<Name>aaa</Name>
<ID>1</ID>
<Name>bbb</Name>
<ID>1</ID>
<Name>ccc</Name>
<ID>1</ID>
<Name>ddd</Name>
<ID>1</ID>
<Name>eee</Name>

Для шага 2: если вы укажете строку нулевой длины, элемент переноса не будет создан.

3. 

    SELECT   

           Name  
    FROM temp1
    FOR XML PATH('');

    Ouput:
    <Name>aaa</Name>
    <Name>bbb</Name>
    <Name>ccc</Name>
    <Name>ddd</Name>
    <Name>eee</Name>

4. SELECT   
        ',' +Name  
FROM temp1
FOR XML PATH('')

Ouput:
,aaa,bbb,ccc,ddd,eee

На шаге 4 мы объединяем значения.

5. SELECT ID,
    abc = (SELECT   
            ',' +Name  
    FROM temp1
    FOR XML PATH('') )
FROM temp1

Ouput:
1   ,aaa,bbb,ccc,ddd,eee
1   ,aaa,bbb,ccc,ddd,eee
1   ,aaa,bbb,ccc,ddd,eee
1   ,aaa,bbb,ccc,ddd,eee
1   ,aaa,bbb,ccc,ddd,eee


6. SELECT ID,
    abc = (SELECT   
            ',' +Name  
    FROM temp1
    FOR XML PATH('') )
FROM temp1 GROUP by iD

Ouput:
ID  abc
1   ,aaa,bbb,ccc,ddd,eee

На шаге 6 мы группируем дату по идентификатору.

STUFF (source_string, start, length, add_string) Параметры или Аргументы source_string Исходная строка для изменения. start Позиция в source_string для удаления длинных символов, а затем вставьте add_string. длина Количество символов, которые нужно удалить из исходной строки. add_string Последовательность символов для вставки в source_string в начальной позиции.

SELECT ID,
    abc = 
    STUFF (
        (SELECT   
                ',' +Name  
        FROM temp1
        FOR XML PATH('')), 1, 1, ''
    )
FROM temp1 GROUP by iD

Output:
-----------------------------------
| Id        | Name                |
|---------------------------------|
| 1         | aaa,bbb,ccc,ddd,eee |
-----------------------------------
Неха Чопра
источник
1
Вы пишете: «На шаге 4 мы объединяем значения». Но неясно, почему / как ','указанный столбец as в сочетании с ('')путем после XML вызывает конкатенацию
barlop
На шаге 4 при выполнении любой строковой операции будет использоваться указанный элемент обтекания, который в этом случае пуст ('').
vCillusion
2
Для тех, кто интересуется пунктом 4 и почему <Имя> исчезает. Это связано с тем, что после объединения имени с запятой больше не столбец, а просто значение, поэтому SQL Server не знает, какое имя для тега xml следует использовать. Например , этот запрос SELECT 'a' FROM some_table FOR XML PATH('')будет производить: 'aaaaaaa'. Но если будет указано имя столбца: SELECT 'a' AS Col FROM some_table FOR XML PATH('')вы получите результат:<Col>a</Col><Col>a</Col><Col>a</Col>
Anth
23

В базе данных SQL Azure и SQL Server появилась очень новая функциональность (начиная с 2017 года) для обработки этого точного сценария. Я полагаю, что это послужит родным официальным методом для того, чего вы пытаетесь достичь с помощью метода XML / STUFF. Пример:

select id, STRING_AGG(name, ',') as abc
from temp1
group by id

STRING_AGG - https://msdn.microsoft.com/en-us/library/mt790580.aspx

РЕДАКТИРОВАТЬ: Когда я первоначально опубликовал это, я упомянул SQL Server 2016, как я думал, я видел это на потенциальной функции, которая должна была быть включена. Либо я вспомнил, что неправильно, либо что-то изменилось, спасибо за предложенную редакцию, исправляющую версию. Кроме того, это произвело большое впечатление на меня, и он не был полностью осведомлен о многоэтапном процессе проверки, который просто потянул меня за окончательным вариантом.

Брайан Джорден
источник
3
STRING_AGG отсутствует в SQL Server 2016. Говорят, что он входит в vNext.
N8allan
К сожалению, я не хотел перезаписывать редактирование из @lostmylogin, извините за это ... То есть тот, кто фактически протолкнул редактирование исправления.
Брайан Джорден
5

В for xml path, если мы определим любое значение , как [ for xml path('ENVLOPE') ]то эти теги будут добавлены в каждой строке:

<ENVLOPE>
</ENVLOPE>
Викас
источник
2
SELECT ID, 
    abc = STUFF(
                 (SELECT ',' + name FROM temp1 FOR XML PATH ('')), 1, 1, ''
               ) 
FROM temp1 GROUP BY id

Здесь в вышеприведенном запросе функция STUFF используется, чтобы просто удалить первую запятую (,)из сгенерированной строки XML, (,aaa,bbb,ccc,ddd,eee)после чего она станет (aaa,bbb,ccc,ddd,eee).

И FOR XML PATH('')просто преобразует данные столбца в (,aaa,bbb,ccc,ddd,eee)строку, но в PATH мы передаем '', чтобы он не создавал тег XML.

И в конце мы сгруппировали записи, используя столбец ID .

Махендра Сингх Дхами
источник
2

Я сделал отладку и, наконец, вернул свой «заполненный» запрос на него, это нормальный способ.

Просто

select * from myTable for xml path('myTable')

дает мне содержимое таблицы для записи в таблицу журнала из отлаженного триггера.

SlavaTT
источник
1
Declare @Temp As Table (Id Int,Name Varchar(100))
Insert Into @Temp values(1,'A'),(1,'B'),(1,'C'),(2,'D'),(2,'E'),(3,'F'),(3,'G'),(3,'H'),(4,'I'),(5,'J'),(5,'K')
Select X.ID,
stuff((Select ','+ Z.Name from @Temp Z Where X.Id =Z.Id For XML Path('')),1,1,'')
from @Temp X
Group by X.ID
Омкар Наик
источник
-1

STUFF ((ВЫБЕРИТЬ разные ',' + CAST (T.ID) ИЗ таблицы T, где T.ID = 1 FOR XML PATH ('')), 1,1, '') AS Name

B.Nishan
источник
-3

Я часто использую с предложением где

SELECT 
TapuAda=STUFF(( 
SELECT ','+TBL.TapuAda FROM (
SELECT TapuAda FROM T_GayrimenkulDetay AS GD 
INNER JOIN dbo.T_AktiviteGayrimenkul AS AG ON  D.GayrimenkulID=AG.GayrimenkulID WHERE 
AG.AktiviteID=262
) AS TBL FOR XML PATH ('')
),1,1,'')
sbaysal
источник
2
Я не понимаю, как это ответ, не могли бы вы дать некоторые объяснения, пожалуйста?
Гар