С-инструкция внутри Наружного применения

#sql-server #with-statement #for-xml-path

Вопрос:

Я использую for xml path для агрегирования значений:

 select max(x.Ids), max(x.Number), (select country,city for json path) as [Json]
from t
outer apply (
    select Stuff((select ','   Convert(varchar(10),t2.Id)
    from t t2
    where t2.city=t.city and t2.country=t.country
    for xml path(''),type).value('(./text())[1]','varchar(10)'),1,1,'') as Ids,
    Stuff((select ','   Convert(varchar(10),t2.Number)
    from t t2
    where t2.city=t.city and t2.country=t.country
    for xml path(''),type).value('(./text())[1]','varchar(10)'),1,1,'') as Numbers
)x
 

Варианты выбора/запросы внутри outer apply идентичны. Интересно, можно ли повторно использовать этот запрос? Я попытался создать CTE внутри outer apply , но это, похоже, не принимается SQL Server:

 select max(x.Ids), max(x.Number), (select country,city for json path) as [Json]
from t
outer apply (
    with Test_CTE( Id, Number) AS (
        SELECT ID, Number FROM t t2
        where t2.city=t.city and t2.country=t.country
    )
    select Stuff((select ','   Convert(varchar(10),t2.Id)
    from Test_CTE t2        
    for xml path(''),type).value('(./text())[1]','varchar(10)'),1,1,'') as Ids,
    Stuff((select ','   Convert(varchar(10),t2.Number)
    from Test_CTE t2 
    for xml path(''),type).value('(./text())[1]','varchar(10)'),1,1,'') as Numbers
)x
 

Приведенная выше попытка приводит к следующей ошибке:

Неправильный синтаксис рядом с ключевым словом «с».

Комментарии:

1. Таким образом, вы не можете использовать общее табличное выражение. Какую версию SQL Server вы используете?

2. Разблокируйте подзапросы, CTE должны быть определены в начале инструкции. С CTE КАК ({оператор select здесь}) должно произойти до фактического оператора select.

3. CTE определяются в начале вашего старта, а не в середине.

Ответ №1:

В свой for xml вы можете включить оба столбца , избежать объединения с», » и сохранить имена столбцов, чтобы выходные данные содержали значения, заключенные в элементы.

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

Кроме того, вы можете рассмотреть возможность применения distinct условия к вашему источнику, прежде чем начинать экономить некоторую обработку.

Итак, для таких данных, как это:

 declare @t table (Id int, Number int, country char(1), city char(1));
insert @t values 
    (1, 101, 'a', 'z'),
    (2, 102, 'b', 'y'),
    (3, 103, 'a', 'z');
 

Вы можете это сделать:

 select      [Json] = (select t.country, t.city for json path),
        
            Ids = stuff(replace(replace(convert(varchar(max),
                ap._xml.query('./Id')
            ),'<Id>', ','),'</Id>',''), 1, 1, ''),
        
            Nums = stuff(replace(replace(convert(varchar(max),
                ap._xml.query('./Number')
            ),'<Number>', ','),'</Number>',''), 1, 1, '')

from        (select distinct t.city, t.country from @t t) t
cross apply (select _xml = (
                select  t2.Id, 
                        t2.Number
                from    @t t2
                where   t2.city = t.city and t2.country = t.country 
                for     xml path(''), type
            )) ap;
 

И получите такой результат:

Json Идентификаторы Тупицы
[{«страна»:»b»,»город»:»y»}] 2 102
[{«страна»:»a»,»город»:»z»}] 1,3 101,103

Но я не знаю, считаете ли вы, что новый подход более элегантен, чем оригинальный. Я бы сказал, что это так, но все равно довольно уродливо. Имейте в виду, что в более новых версиях sql server есть string_agg функция, которая делает все это за вас.

Комментарии:

1. Спасибо! Это было именно то, что я искал. Это дало мне значительный прирост производительности по сравнению со старым решением. Я нахожусь на SQL Server 2016, следовательно, «для пути XML», а не string_agg. Одна вещь, которую я заметил, заключается в том, что если значение в идентификаторе или номере отсутствует, то вместо этого материал/замена оставит <Идентификатор/> или <Идентификатор/><Номер/>. Но это, конечно, можно заменить.

2. @Charlieface, суть здесь в агрегировании строк: превращении строковых значений в столбце в csv в одной ячейке. .value извлекает только первый элемент, а это не то, что нужно.