Как Stuff и "для Xml Path" работают на сервере Sql

Таблица:

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

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

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

Query:

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

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

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

Ответ 1

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

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, чтобы получить полный список

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

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 |
-----------------------------------

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

Ответ 2

В этой статье рассматриваются различные способы объединения строк в 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,...'

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

Ответ 3

Режим 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 для удаления символов длины, а затем вставить add_string. длина Количество символов для удаления из source_string. add_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 |
-----------------------------------

Ответ 4

В Azure SQL Database и 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

EDIT: Когда я изначально разместил это, я упомянул SQL Server 2016, поскольку, как я думал, я видел это по потенциальной функции, которая должна была быть включена. Либо я вспомнил, что неправильно или что-то изменилось, спасибо за предлагаемое исправление исправления версии. Кроме того, довольно впечатлен и не был полностью осведомлен о многоступенчатом процессе обзора, который просто втянул меня в окончательный вариант.

Ответ 5

In для пути xml Если мы определим любое значение, подобное [для пути xml ('ENVLOPE'), тогда

выше теги будут добавлены с каждой строкой.

Ответ 6

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

Ответ 7

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.

Ответ 8

Я отлаживал и, наконец, возвращал свой "stuffed" запрос к нему обычным способом.

Просто

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

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