Как устроена группировка данных в запросах 1С 8.3 и почему она тормозит

· 10 мин

Содержание

Группировка в запросе 1С — это секция СГРУППИРОВАТЬ ПО: она сворачивает строки с одинаковыми значениями полей в одну и считает по ним агрегатные функции СУММА, КОЛИЧЕСТВО, МАКСИМУМ, МИНИМУМ, СРЕДНЕЕ. Каждое поле в ВЫБРАТЬ, которое не обёрнуто в агрегатную функцию, обязано стоять в группировке. Иначе получите ошибку Поле не входит в группу.

Частое заблуждение: «группировкой запросов» называют пакетный запрос, когда несколько запросов отправляют одним текстом через ;. Это другой механизм. Пакет экономит обращения к серверу, а группировка меняет сам результат: из тысяч строк движений получается одна строка на товар, склад или контрагента. Всё ниже относится к платформе 8.3 и одинаково для любых конфигураций: БП 3.0, ЗУП 3.1, УТ 11.

Как платформа выполняет группировку в запросе

Запрос на языке 1С не исполняется «сам по себе». Платформа переводит его в SQL для конкретной СУБД.

  • 🗄️ Клиент-серверная база (MS SQL Server, PostgreSQL). СГРУППИРОВАТЬ ПО превращается в GROUP BY. Считает рабочий процесс СУБД, а сервер 1С получает уже свёрнутые строки.
  • 📁 Файловая база. Группировку выполняет встроенный движок платформы прямо в процессе клиента или веб-сервера. Отсюда разница в скорости: один и тот же отчёт на файловой базе в 10 ГБ может считаться минуты.
  • 🔗 Разыменование ссылок. Поле вида Товары.Номенклатура.Родитель в группировке добавляет в SQL соединение с таблицей справочника. Каждая точка в пути — ещё одно соединение.

Отсюда первый практический вывод: тяжесть группировки определяет не число строк результата, а число строк, которые СУБД прочитает до свёртки. Отчёт на 20 строк может перебрать 5 млн записей регистра.

Где группировка встречается кроме текста запроса

ГдеКак задаётсяКто считает
Текст запросаСГРУППИРОВАТЬ ПО, ИТОГИ ПОСУБД, итоги — частично сервер 1С
Виртуальные таблицы регистров (Остатки, Обороты)Неявно, по измерениямСУБД, по таблицам итогов
Схема компоновки данных (СКД)Структура отчёта: группировки, детальные записиПлатформа строит запрос сама, затем компонует результат
Динамический списокНастройка «Группировка» в форме спискаПлатформа, при каждом открытии

Синтаксис группировки в языке запросов 1С

Базовый пример — продажи по номенклатуре за период с отбором крупных позиций:

ВЫБРАТЬ
    Продажи.Номенклатура КАК Номенклатура,
    СУММА(Продажи.Количество) КАК Количество,
    СУММА(Продажи.Сумма) КАК Сумма,
    КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Продажи.Ссылка) КАК ЧислоДокументов
ИЗ
    Документ.РеализацияТоваровУслуг.Товары КАК Продажи
ГДЕ
    Продажи.Ссылка.Проведен
    И Продажи.Ссылка.Дата МЕЖДУ &НачалоПериода И &КонецПериода
СГРУППИРОВАТЬ ПО
    Продажи.Номенклатура
ИМЕЮЩИЕ
    СУММА(Продажи.Сумма) > 100000

Три секции и их порядок

  1. ГДЕ отбирает строки до группировки. Сюда ставьте все условия на исходные поля.
  2. СГРУППИРОВАТЬ ПО сворачивает оставшиеся строки.
  3. ИМЕЮЩИЕ фильтрует уже свёрнутые группы. Здесь работают условия на агрегаты: «сумма больше 100 000», «документов больше трёх».

Условие на обычное поле можно написать и в ИМЕЮЩИЕ, но это лишняя работа: СУБД сначала сгруппирует всё, а потом выбросит часть. Такие условия место в ГДЕ.

Агрегатные функции и NULL

  • ➕ СУММА и СРЕДНЕЕ — только для чисел.
  • 🔢 КОЛИЧЕСТВО(*) считает строки, КОЛИЧЕСТВО(Поле) — непустые значения, КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Поле) — уникальные.
  • ⬆️ МАКСИМУМ и МИНИМУМ работают с числами, датами и строками.
  • ⚠️ Если в группе нет ни одного значения, СУММА вернёт NULL, а не ноль. При соединении с другой таблицей оборачивайте: ЕСТЬNULL(СУММА(...), 0).

Группировка без агрегатов и РАЗЛИЧНЫЕ

ВЫБРАТЬ РАЗЛИЧНЫЕ Поле и ВЫБРАТЬ Поле ... СГРУППИРОВАТЬ ПО Поле дают одинаковый результат. На СУБД они обычно выполняются одинаково. Выбирайте по смыслу: если дальше нужны агрегаты — группировка, если просто убрать дубли — РАЗЛИЧНЫЕ.

ИТОГИ: группировка поверх детальных строк

Секция ИТОГИ не сворачивает данные, а добавляет над ними строки-итоги. Детальные записи остаются:

ВЫБРАТЬ
    Остатки.Склад КАК Склад,
    Остатки.Номенклатура КАК Номенклатура,
    Остатки.КоличествоОстаток КАК Количество
ИЗ
    РегистрНакопления.ТоварыНаСкладах.Остатки(&Дата) КАК Остатки
ИТОГИ
    СУММА(Количество)
ПО
    ОБЩИЕ,
    Склад

Имя регистра условное — в вашей конфигурации оно может быть другим. Обходят такой результат через Результат.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам): сначала строка общего итога, внутри — склады, внутри складов — детальные строки. Для справочников с иерархией есть ПО Номенклатура ИЕРАРХИЯ — итоги по каждой группе справочника.

В свежих версиях платформы появилась конструкция СГРУППИРОВАТЬ ПО ГРУППИРУЮЩИМ НАБОРАМ — несколько разрезов свёртки в одном запросе. Если конструктор запроса её не предлагает, ваша версия платформы её ещё не поддерживает; точный синтаксис сверяйте в синтакс-помощнике.

Ошибки группировки и как их читать

Большая часть проблем с группировкой — не производительность, а неверный результат или отказ запроса выполняться.

Диагностика: выберите симптом

Параметры кластера и СУБД, которые влияют на тяжёлую группировку

Сама группировка в настройках кластера не регулируется: отдельного «параметра группировки» нет. Но тяжёлый запрос со свёрткой миллионов строк упирается в память — и на стороне 1С, и на стороне СУБД.

Сервер 1С

В консоли администрирования серверов, в свойствах рабочего сервера, задаются пороги памяти рабочих процессов, в том числе Временно допустимый объём памяти процессов и Критический объём памяти процессов. Если отчёт тянет в рабочий процесс огромный результат (например, группировка с ИТОГИ по детальным записям за год), процесс может превысить порог. Тогда платформа прервёт вызов, а пользователь увидит ошибку нехватки памяти.

Порядок действий:

  1. Найдите отчёт, который вызвал рост памяти: события CALL и EXCP в технологическом журнале.
  2. Уменьшите объём, который уходит на сервер 1С: свёртку делайте в запросе, а не в коде или в макете отчёта.
  3. Только после этого поднимайте пороги — и с учётом свободной памяти сервера.

Изменение параметров рабочего сервера и перезапуск службы агента сервера 1С обрывают сеансы пользователей. Делайте это вне рабочего времени и с предупреждением.

СУБД

  • 🐘 PostgreSQL. Хеш-группировка держит таблицу групп в памяти размером до work_mem. Не хватает — данные уходят во временные файлы на диск, запрос замедляется в разы. Параметр меняют аккуратно: он действует на каждую операцию в каждом соединении.
  • 🪟 MS SQL Server. Большие сортировки и группировки при нехватке памяти сбрасываются в tempdb. Медленный диск под tempdb — частая причина тормозов именно у отчётов со свёрткой.
  • 📊 Статистика. Устаревшая статистика ведёт к неверной оценке числа строк и плохому плану. Регламентное обновление статистики на СУБД — обязательная часть обслуживания любой базы 1С.

Блокировки и взаимоблокировки при чтении сгруппированных данных

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

Классический сценарий взаимоблокировки

  1. Два пользователя одновременно проводят расходные документы по одному складу.
  2. Каждый сеанс в транзакции читает остатки запросом к Остатки с группировкой по номенклатуре.
  3. Оба получили остатки и хотят записать движения в те же записи регистра.
  4. Каждый ждёт, пока другой отпустит данные. Один из сеансов получает ошибку со словами Конфликт блокировок при выполнении транзакции или превышение времени ожидания блокировки.

Как это лечат в управляемом режиме блокировок

Перед чтением остатков накладывают исключительную управляемую блокировку только на нужные записи. Тогда второй сеанс подождёт на входе, а не сцепится с первым посреди транзакции:

Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрНакопления.ТоварыНаСкладах");
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
ЭлементБлокировки.ИсточникДанных = ТаблицаТоваров;
ЭлементБлокировки.ИспользоватьИзИсточникаДанных("Номенклатура", "Номенклатура");
ЭлементБлокировки.УстановитьЗначение("Склад", Склад);
Блокировка.Заблокировать();
// дальше — запрос остатков с группировкой и запись движений

Здесь ТаблицаТоваров — таблица значений с колонкой Номенклатура, свёрнутая по товарам заранее. Блокировка ставится внутри транзакции: в обработке проведения она уже открыта платформой.

Что ещё снижает конфликты

  • 🎯 Блокируйте узко: по складу и товарам документа, а не весь регистр.
  • ⏱️ Держите транзакцию короткой. Тяжёлый аналитический запрос с группировкой за год не место внутри проведения.
  • 📑 Отчёты выполняйте вне транзакций. Чтение вне транзакции в управляемом режиме не ставит управляемых блокировок 1С.
  • 🧾 Ищите виновника в технологическом журнале: события TLOCK, TTIMEOUT, TDEADLOCK показывают, кто кого ждал и на каком пространстве блокировок.

Как увидеть и улучшить план запроса с группировкой

План выполнения показывает, как СУБД читает данные: по индексу или полным просмотром, хешем или сортировкой группирует.

Где взять план

  • 📝 Технологический журнал 1С. В logcfg.xml включают свойство <plansql/> и события DBMSSQL (MS SQL) или DBPOSTGRS (PostgreSQL). В журнал попадут текст SQL и план.
  • 🔍 Средства СУБД. Для MS SQL — фактический план в SQL Server Management Studio, для PostgreSQL — EXPLAIN (ANALYZE, BUFFERS) на тексте SQL из журнала.
  • 📁 Файловая база планов не показывает. Если отчёт тормозит только в ней, проверьте тот же запрос на копии в клиент-серверном варианте.

Что искать в плане

Признак в планеЧто значитЧто менять в запросе 1С
Полный просмотр большой таблицыОтбор не попадает в индексУсловия на измерения — в параметры виртуальной таблицы, а не в ГДЕ
Сортировка или хеш с выгрузкой на дискГруппируется слишком много строкСузить период, сгруппировать раньше во временной таблице
Много соединений со справочникамиРазыменование полей через точкуГруппировать по ссылке, реквизиты получать после свёртки
Вложенные циклы с огромным числом итерацийНеверная оценка строкОбновить статистику, разбить запрос на пакет с временными таблицами

Главное правило для регистров

Не группируйте таблицу движений, если нужен остаток или оборот. Виртуальные таблицы Остатки и Обороты берут данные из таблиц итогов, где свёртка уже сделана. Условие в параметрах виртуальной таблицы отбирает записи до расчёта:

ИЗ
    РегистрНакопления.ТоварыНаСкладах.Остатки(
        &Дата,
        Склад = &Склад) КАК Остатки

То же условие в ГДЕ применится уже к рассчитанным остаткам по всем складам — дольше в разы на больших базах.

Переписываем код: от циклов к одной группировке

Чаще всего ускорение даёт не настройка сервера, а переделка кода. Три приёма, которые знает каждый, кто оптимизировал отчёты.

1. Запрос в цикле → один запрос с группировкой

Плохо: перебрать 3 000 товаров и для каждого выполнить запрос суммы продаж. Это 3 000 обращений к СУБД. Хорошо: один запрос со списком товаров во временной таблице, соединение и СГРУППИРОВАТЬ ПО. Обращение одно, результат тот же.

2. Сначала свернуть, потом соединять

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

ВЫБРАТЬ
    Продажи.Контрагент КАК Контрагент,
    СУММА(Продажи.СуммаДокумента) КАК Сумма
ПОМЕСТИТЬ ВТ_Продажи
ИЗ
    Документ.РеализацияТоваровУслуг КАК Продажи
ГДЕ
    Продажи.Проведен
    И Продажи.Дата МЕЖДУ &НачалоПериода И &КонецПериода
СГРУППИРОВАТЬ ПО
    Продажи.Контрагент
ИНДЕКСИРОВАТЬ ПО
    Контрагент
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ_Продажи.Контрагент КАК Контрагент,
    ВТ_Продажи.Сумма КАК Продано,
    ЕСТЬNULL(ВТ_Оплаты.Сумма, 0) КАК Оплачено
ИЗ
    ВТ_Продажи КАК ВТ_Продажи
        ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Оплаты КАК ВТ_Оплаты
        ПО ВТ_Продажи.Контрагент = ВТ_Оплаты.Контрагент

ВТ_Оплаты строится так же, отдельным запросом пакета. Так строки не размножаются, а соединение идёт по уже маленьким таблицам. ИНДЕКСИРОВАТЬ ПО имеет смысл, когда во временной таблице тысячи строк; для пары десятков он не нужен.

3. Группировка по ссылке, а не по реквизиту через точку

СГРУППИРОВАТЬ ПО Товары.Номенклатура.Родитель заставляет СУБД соединять табличную часть со справочником до свёртки — на каждой строке. Сгруппируйте по Товары.Номенклатура во временную таблицу, а родителя получите вторым запросом уже по свёрнутым строкам. При 1 млн строк продаж и 5 000 товаров соединение со справочником пройдёт по 5 000 строкам вместо миллиона.

Когда группировка в запросе не нужна

  • 📊 Отчёт на СКД: группировки задаются в структуре отчёта. Ручная СГРУППИРОВАТЬ ПО в наборе данных отнимает у пользователя возможность менять разрезы.
  • 🔁 Нужны и детальные строки, и итоги: ИТОГИ или СКД, а не два отдельных запроса.
  • 🧮 Данные уже в таблице значений на сервере: метод Свернуть("Номенклатура", "Количество, Сумма") дешевле нового запроса.
Проверка запроса с группировкой перед выпуском
Выполнено: 0 / 7

Частые вопросы о группировке в запросах

Можно ли сгруппировать по выражению, а не по полю?
Да. В СГРУППИРОВАТЬ ПО допустимы выражения: НАЧАЛОПЕРИОДА(Дата, МЕСЯЦ), ВЫБОР КОГДА … КОНЕЦ, ВЫРАЗИТЬ. То же выражение нужно повторить в ВЫБРАТЬ.
Как сгруппировать продажи по месяцам?
Выберите НАЧАЛОПЕРИОДА(Продажи.Дата, МЕСЯЦ) КАК Месяц и сгруппируйте по этому же выражению. Для регистров удобнее виртуальная таблица Обороты с периодичностью Месяц.
Чем ИТОГИ отличаются от СГРУППИРОВАТЬ ПО?
СГРУППИРОВАТЬ ПО заменяет детальные строки свёрнутыми. ИТОГИ оставляет детальные строки и добавляет над ними строки с суммами по уровням.
Почему в конструкторе запроса вкладка «Группировка» меняет мои поля?
Конструктор сам переносит в группировку все поля без агрегатных функций. Если поле должно суммироваться, перетащите его в список суммируемых полей и выберите функцию.
Влияет ли порядок полей в СГРУППИРОВАТЬ ПО на результат?
На набор строк — нет. Порядок вывода задаёт только УПОРЯДОЧИТЬ ПО; без него СУБД вправе вернуть строки в любом порядке.