Як працюють речі та "для шляху Xml" на сервері Sql


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. Приєднайтесь до ідентифікатора, щоб отримати повний список

Далі ми просто приєднуємося до цього у списку ідентифікаторів у темп-таблиці, щоб отримати список ідентифікаторів з іменем:

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

Сподіваюся, це допомагає!


57
Ви повинні працювати в команді з документації Microsoft (якщо така є)
Fandango68

55
@ Fandango68, @ FutbolFan - Він не може працювати в команді з документації Microsoft. Його пояснення занадто чіткі і занадто прямі. ;-)
Кріс

1
@ChrisProsser Я згоден. Oracle випередив Microsoft у цьому, ввівши LISTAGGфункцію в Oracle 11gR2. Я втрачаю цю функціональність у дні, коли мені доводиться використовувати це замість цього. techonthenet.com/oracle/functions/listagg.php
FutbolFan

2
Привіт. На кроці 1, якщо ви зробите: ВИБІРТЕ ім'я ІЗ temp1 ДЛЯ XML-ПАТ ('') ... ви отримаєте <ім'я> aaa</name> <ім'я> bbb </name> ... і т.д. ... я не ' Зрозумійте це спочатку ... Змінивши його на SELECT '' + ім'я ... і т.д. ..., видаляє теги.
KevinVictor

1
@ChrisProsser - Sybase ASA listфункціонував десятиліттями. На жаль, Microsoft базується на SQLServer на базі ASE Sybase, і ніколи не переймався функцією списку до минулого року. Я згоден - це розумно. І тоді вони роблять, це називають string_agg. Я вважав би listдосить очевидним.
youcantryreachingme

75

Ця стаття охоплює різні способи об'єднання рядків у 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
Яке використання типу у вашому запиті. Я вважаю, що для визначення результат 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. length Кількість символів, які потрібно видалити з source_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 ми об'єднуємо значення". Але незрозуміло, чому / як ','зазначений стовпчик у поєднанні з ('')контуром після xml викликає конкатенацію
barlop

На кроці 4, виконуючи будь-яку операцію з рядком, буде використаний вказаний елемент обгортки, який є порожнім ('') для цього випадку.
vCillusion

1
Для всіх, хто цікавиться питанням 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>
1818

23

Є дуже нова функціональність у базах даних Azure SQL і 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, оскільки думав, що бачив це в потенційній функції, яка повинна бути включена. Або я згадав, що неправильно або щось змінилось, дякую за запропоновану редакцію, що виправляє версію. Крім того, я був дуже вражений і не до кінця усвідомлював багатоступеневий процес перегляду, який просто підтягнув мене до остаточного варіанту.


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.

І наприкінці ми згрупували записи за допомогою стовпця ІД .


2

Я зробив налагодження і, нарешті, повернув свій «фарширований» запит на це нормальним способом.

Просто

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

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


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 ((SELECT razloct ',' + CAST (T.ID) З таблиці T, де T.ID = 1 ДЛЯ XML PATH ('')), 1,1, '') AS Назва


-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,'')

2
Я не бачу, як це відповідь, чи могли б ви підкинути якісь пояснення?
Гар
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.