+= в предложении SELECT; константа против столбца

Я наблюдал некоторое странное поведение в MS SQL Server 2017.

  • += в select действует как агрегатор ("объединить значения из всех строк"), когда справа находится константа.
  • += в select действует как "просто установите значение", когда справа находится имя столбца. (также это влияет на поведение агрегирования для других столбцов)

Итак, мои вопросы:

  1. Почему результат @c1 содержит значение только последней строки, даже если используется +=?
  2. Почему @c2 затронуто изменением +=->= для @c1?

Версия 1:

BEGIN
    DECLARE
        @c1 NVARCHAR(MAX) = N'',
        @c2 NVARCHAR(MAX) = N'';

    SELECT
        @c1 = constraint_name, -- version-1
        @c2 += '+'
    FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS
    ORDER BY 1 DESC
    ;

    PRINT '@c1=' + @c1;
    PRINT '@c2=' + @c2;
END
;

Результат версии 1:

@c1 = fk_abcde
@c2 = ++++++++++++++++++++++++++++++++++++++++++
(`@c2` result is aggregation of many rows; one plus for each row)

Версия 2:

BEGIN
    DECLARE
        @c1 NVARCHAR(MAX) = N'',
        @c2 NVARCHAR(MAX) = N'';

    SELECT
        @c1 += constraint_name, -- version-2
        @c2 += '+'
    FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS
    ORDER BY 1 DESC
    ;

    PRINT '@c1=' + @c1;
    PRINT '@c2=' + @c2;
END
;

Версия 2 Результат:

@c1 = fk_abcde
@c2 = +
(`@c2` is just value assigned from last processed row)

Это кажется очень странным - вроде как ошибка. Я не могу найти какие-либо документы об этом. doc on '+= string' вообще не упоминайте += использование в select запросе.

(на данный момент моя цель - полностью понять поведение, чтобы я случайно не наступил на него. Любые подсказки к правильной документации/ключевым словам для поиска были бы полезны)


person IndustryUser1942    schedule 27.02.2019    source источник
comment
Он находится не в том месте документации, поэтому неудивительно, что вы не нашел: не используйте переменную в операторе SELECT для конкатенации значений (т. значения). Могут возникнуть неожиданные результаты запроса. Потому что все выражения в списке SELECT (включая присваивания) не обязательно выполняются ровно один раз для каждой выходной строки.   -  person Damien_The_Unbeliever    schedule 27.02.2019
comment
@Damien_The_Unbeliever Чтобы добавить к вашему комментарию, я собирался предположить, что то, что мы видим во втором случае, - это просто конкатенация переменных, происходящая один раз, а в других случаях игнорируемая или отбрасываемая.   -  person Tim Biegeleisen    schedule 27.02.2019
comment
И еще кое-что: этот подход называется причудливое обновление. и в большинстве случаев этого следует избегать...   -  person Shnugo    schedule 27.02.2019
comment
@Damien_The_Unbeliever Я считаю, что ваш комментарий и ссылка на документы должны быть здесь ответом.   -  person EzLo    schedule 27.02.2019
comment
@IndustryUser1942, что ты пытаешься сделать? Если вы хотите агрегировать строки, используйте STRING_AGG в SQL Server 2017. Существуют и другие способы сделать то же самое в предыдущих версиях, все они описаны в Статьи Аарона Бертрана. Самый быстрый и масштабируемый способ — использовать SQLCLR UDF или FOR XML.   -  person Panagiotis Kanavos    schedule 27.02.2019
comment
@IndustryUser1942 то, что вы опубликовали, - это хак, который часто используется в MySQL, но там он тоже не работает. Это зависит от сервера, использующего очень специфический способ выполнения запроса и получения результатов, которые меняются от версии к версии и никогда не могут включать параллельное выполнение.   -  person Panagiotis Kanavos    schedule 27.02.2019
comment
@Damien_The_Unbeliever Пожалуйста, скопируйте свой комментарий (ссылка + цитата) в качестве ответа.   -  person IndustryUser1942    schedule 28.02.2019
comment
@PanagiotisKanavos Изначально я хотел агрегировать/объединять строки. Для этого я буду использовать STRING_AGG. Спасибо.   -  person IndustryUser1942    schedule 28.02.2019


Ответы (3)


Он находится в неправильное место в документации, поэтому неудивительно, что вы его не нашли:

Не используйте переменную в операторе SELECT для объединения значений (то есть для вычисления агрегированных значений). Могут возникнуть неожиданные результаты запроса. Потому что все выражения в списке SELECT (включая присваивания) не обязательно выполняются ровно один раз для каждой выходной строки.

Лучше искать разные способы конкатенации строк. Если ваша версия поддерживает это, выберите STRING_AGG. Для более ранних версий Аарон Бертран предоставил хороший набор options (спасибо Панайотису Канавосу за предоставление ссылки)

person Damien_The_Unbeliever    schedule 28.02.2019

Ну, что я обнаружил, довольно интересно: без ORDER BY запрос работает стабильно и ожидаемо.

Но при добавлении ORDER BY caluse я получаю те же результаты, что и вы.

Я предлагаю использовать CTE с упорядочением - к сожалению, упорядочение не разрешено в CTE или подзапросах, но обходным путем является использование ключевого слова TOP, которое позволяет нам упорядочивать в таких ситуациях.

См. сценарий ниже:

;with cte as (
    select top 100 percent table_schema
    from information_schema.columns
    order by 1
)
-- This is more reliable
select @c1 = table_schema, @c2 += '+' from cte
person Michał Turczyn    schedule 27.02.2019
comment
Есть причина, по которой это называется quirky update и не рекомендуется. Он срабатывает случайно и всегда может дать сбой даже среди второстепенных версий исправлений или даже если, например, параллельная операция изменяет способ обработки результатов. - person Panagiotis Kanavos; 27.02.2019

Вы должны использовать Order by constraint_name вместо Order by 1, потому что вы присваиваете значения переменной. Это не оператор выбора.

person Thangadurai.B    schedule 27.02.2019
comment
Неважно, как вы его настроите. Как я уже упоминал в комментариях, использование переменных для агрегирования в SELECT задокументировано как ненадежное по своей сути. - person Damien_The_Unbeliever; 27.02.2019