При обновлении платформы 1С или переносе конфигураций (например, "Управление торговлей") с версии 8.1 на 8.2 и выше (8.3), разработчики часто сталкиваются с ситуацией, когда ранее работавший код начинает выдавать ошибки. Одна из самых распространенных ошибок звучит так: "Ошибка в запросе: неверные параметры "Сумма"".
В этой статье мы подробно разберем, почему возникает эта ошибка, как изменился механизм работы построителя запросов в новых версиях платформы, и рассмотрим несколько способов решения проблемы — от простых "заплаток" до профессиональных стандартов написания кода.
Давайте посмотрим на ситуацию, описанную автором. Имеется сложный запрос, который собирает данные из разных источников (Регистры накопления, Документы) с помощью оператора ОБЪЕДИНИТЬ. После объединения данных происходит группировка и суммирование показателей во внешнем запросе.
Упрощенная структура проблемного запроса выглядит следующим образом:
ВЫБРАТЬ
...
Сумма(НетТТН) как НетТТН
ИЗ
(
ВЫБРАТЬ
...
NULL КАК НетТТН
ИЗ РегистрНакопления...
ОБЪЕДИНИТЬ
ВЫБРАТЬ
...
NULL КАК НетТТН
ИЗ Документ.Реестр...
ОБЪЕДИНИТЬ
ВЫБРАТЬ
...
NULL КАК НетТТН
ИЗ Документ.СХН...
) КАК Данные
СГРУППИРОВАТЬ ПО ...
Обратите внимание на поле НетТТН. В примере автора оно присутствует во всех частях объединения, и во многих случаях оно задано явно как NULL.
Чтобы понять причину, нам нужно углубиться в то, как платформа 1С определяет типы данных в запросах.
Агрегатная функция СУММА предназначена для работы исключительно с числовыми типами данных. Вы не можете сложить строки, даты или ссылки. Также вы не можете сложить "Ничто" (NULL), если система не знает, что это "Ничто" должно быть числом.
В платформе 8.1 механизм типизации был более мягким (или "ленивым"). Если в колонке были только NULL, система могла проигнорировать это на этапе построения плана запроса или неявно привести тип в момент выполнения.
Начиная с версии 8.2, оптимизатор запросов стал строже. Рассмотрим алгоритм его действий:
НетТТН в первой выборке — это NULL.NULL.NULL.НетТТН — это неопределенный тип (NULL), так как нигде не встретилось явного указания на Число, Строку или Дату.Сумма(НетТТН).СУММА к полю, которое не является числом.Теперь, когда мы выяснили причину, давайте перейдем к методам решения.
Самый простой способ подсказать системе, что поле должно быть числовым — это заменить NULL на числовой литерал 0 хотя бы в одном из подзапросов (а лучше во всех для единообразия).
Как только оптимизатор увидит цифру 0, он поймет: "Ага, в этой колонке хранятся числа". Следовательно, тип всей колонки в результате объединения станет Число, и функция СУММА отработает корректно.
Изменим наш код:
ВЫБРАТЬ
...
Сумма(НетТТН) как НетТТН
ИЗ
(
ВЫБРАТЬ
...
0 КАК НетТТН // Было NULL
ИЗ РегистрНакопления...
ОБЪЕДИНИТЬ
ВЫБРАТЬ
...
0 КАК НетТТН // Было NULL
ИЗ Документ.Реестр...
...
Этот метод помог автору темы и является вполне рабочим решением для простых ситуаций.
Хотя замена на ноль работает, в сложной разработке считается хорошим тоном явно указывать типы полей, особенно если запрос может быть использован в Системе Компоновки Данных (СКД) или если важна точность (разрядность) чисел.
Используйте функцию ВЫРАЗИТЬ (CAST). Это дает четкую инструкцию платформе: "Здесь пусто, но считай это полем числового типа определенной длины".
Пример правильного кода:
ВЫБРАТЬ
...
ВЫРАЗИТЬ(NULL КАК ЧИСЛО(15, 2)) КАК НетТТН
ИЗ ...
Преимущества этого метода:
0, платформа может назначить минимальную разрядность, и при последующих объединениях с дробными числами могут возникнуть нюансы округления (хотя в современных версиях SQL Server это обрабатывается корректно, лучше быть явным).Если по каким-то причинам вы не хотите или не можете менять вложенные запросы (например, текст подзапроса формируется динамически и сложно модифицируется), можно попытаться обработать ситуацию на уровне агрегации.
Используйте конструкцию ЕСТЬNULL внутри суммы:
Сумма(ЕСТЬNULL(НетТТН, 0)) как НетТТН
Однако, этот метод может не сработать, если ошибка возникает на этапе предварительной проверки типов (компиляции запроса), а не выполнения. В случае с платформой 8.2+ ошибка "Неверные параметры" часто вылетает именно до выполнения, поэтому система даже не дойдет до вычисления ЕСТЬNULL. Поэтому Решение №1 и №2 являются предпочтительными.
В обсуждении прозвучало предложение заменить функцию СУММА на МАКСИМУМ. Давайте разберем, почему это опасно.
Предложение:
МАКСИМУМ(НетТТН) как НетТТН
Почему это "сработает": Функция МАКСИМУМ (MAX) универсальна. Она умеет сравнивать числа, строки, даты и даже булево. Поэтому ошибка типизации исчезнет.
Почему это неправильно: Вы меняете логику отчета. Функция СУММА складывает все значения в группе. Функция МАКСИМУМ выбирает одно наибольшее.
Пример: У контрагента есть две накладные с долгом 100 рублей и 200 рублей.
СУММА вернет 300 (общий долг).
МАКСИМУМ вернет 200.
Отчет покажет неверные цифры, и бухгалтер будет дезинформирован. Используйте этот метод только если вы абсолютно уверены, что вам не нужно суммирование.
Анализируя код автора, мы видим использование оператора ОБЪЕДИНИТЬ. В контексте задач, где данные потом группируются и суммируются, это часто является ошибкой производительности.
ОБЪЕДИНИТЬ (UNION) — выполняет объединение таблиц с поиском и удалением дублей. Это требует сортировки всех данных, что нагружает сервер.ОБЪЕДИНИТЬ ВСЕ (UNION ALL) — просто "склеивает" таблицы одна за другой без проверок.Поскольку во внешнем запросе у вас стоит СГРУППИРОВАТЬ ПО и СУММА, удаление дублей во вложенном запросе бессмысленно (а иногда и вредно, если две одинаковые суммы должны были сложиться). Для ускорения работы отчета всегда старайтесь использовать ОБЪЕДИНИТЬ ВСЕ.
Чтобы ваш код работал стабильно на любых версиях платформы (8.2, 8.3) и не вызывал ошибок типизации:
ОБЪЕДИНИТЬ, которые заданы как NULL.NULL на 0 или, что еще лучше, на ВЫРАЗИТЬ(NULL КАК ЧИСЛО(...)).ОБЪЕДИНИТЬ на ОБЪЕДИНИТЬ ВСЕ, если логика не требует строгого удаления дубликатов строк перед суммированием.Пример исправленного фрагмента кода:
Функция ЗапросЗаказчик(СтрокаЗапроса)
Запрос = Новый Запрос;
Запрос.Текст="
|Выбрать
| Контрагент,
| ...
| Сумма(НетТТН) как НетТТН
|ИЗ
| (ВЫБРАТЬ
| ...
| 0 КАК НетТТН, // Исправлено: явное число
| ...
| ИЗ РегистрНакопления...
|
| ОБЪЕДИНИТЬ ВСЕ // Исправлено: для производительности
|
| ВЫБРАТЬ
| ...
| Сумма(Реестр.СуммаДокумента*1.18) КАК НетТТН // Здесь уже есть число
| ИЗ ...
| ) КАК Данные
|СГРУППИРОВАТЬ ПО ...";
// Установка параметров и возврат результата
Возврат Запрос.Выполнить().Выбрать();
КонецФункции
Следуя этим простым правилам, вы избежите ошибок "Неверные параметры" и сделаете ваши запросы более быстрыми и надежными.