При разработке отчетов или механизмов контроля взаиморасчетов часто возникает задача: отобрать только те объекты (например, договоры), которые удовлетворяют определенному финансовому условию, основанному на данных из разных источников. В нашем случае необходимо сравнить статичное значение из справочника — СуммаДоговора — с динамически вычисляемой суммой оплат из регистра накопления.
В этой статье мы подробно разберем несколько способов реализации такого условия в запросе, проанализируем ошибки начинающих разработчиков и выясним, какой метод является наиболее оптимальным с точки зрения производительности платформы 1С.
Самый прямой путь решения задачи в рамках одного запроса — использование предложения ИМЕЮЩИЕ. Эта секция в языке запросов 1С предназначена специально для наложения условий на результат агрегатных функций (таких как СУММА, КОЛИЧЕСТВО, МАКСИМУМ).
Разберем важный нюанс: в секции ИМЕЮЩИЕ нельзя использовать псевдонимы полей, которые вы задали в секции ВЫБРАТЬ. Платформа 1С (как и стандартный SQL) требует повторения всего агрегатного выражения.
Рассмотрим пример кода, который решает поставленную задачу:
ВЫБРАТЬ
ДоговорыКонтрагентов.Ссылка КАК Договор,
ДоговорыКонтрагентов.СуммаДоговора КАК СуммаДоговора,
СУММА(ЕСТЬNULL(Оплаты.Сумма, 0)) КАК СуммаОплат
ИЗ
Справочник.ДоговорыКонтрагентов КАК ДоговорыКонтрагентов
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.Оплаты КАК Оплаты
ПО Оплаты.Договор = ДоговорыКонтрагентов.Ссылка
СГРУППИРОВАТЬ ПО
ДоговорыКонтрагентов.Ссылка,
ДоговорыКонтрагентов.СуммаДоговора
ИМЕЮЩИЕ
СУММА(ЕСТЬNULL(Оплаты.Сумма, 0)) >= ДоговорыКонтрагентов.СуммаДоговора
Проанализируем этот запрос. Мы используем ЛЕВОЕ СОЕДИНЕНИЕ, чтобы не потерять договоры, но основная фильтрация происходит в конце. Если сумма по всем записям регистра меньше суммы, указанной в справочнике, такая строка просто не попадет в итоговую выборку.
Профессиональная разработка на 1С подразумевает отказ от прямого обращения к физическим таблицам регистров накопления, если можно использовать виртуальные таблицы. Прямое обращение к таблице РегистрНакопления.Оплаты заставляет систему сканировать все записи, что крайне неэффективно при больших объемах данных.
Выясним причину, почему виртуальная таблица Обороты лучше: она уже содержит предварительно рассчитанные итоги. Использование этой таблицы значительно ускоряет выполнение запроса.
Рассмотрим правильную структуру запроса:
ВЫБРАТЬ
ДоговорыКонтрагентов.Ссылка КАК Договор,
ДоговорыКонтрагентов.СуммаДоговора КАК СуммаДоговора,
ЕСТЬNULL(ОплатыОбороты.СуммаОборот, 0) КАК Оплачено
ИЗ
Справочник.ДоговорыКонтрагентов КАК ДоговорыКонтрагентов
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.Оплаты.Обороты(,,,) КАК ОплатыОбороты
ПО ОплатыОбороты.Договор = ДоговорыКонтрагентов.Ссылка
ГДЕ
ЕСТЬNULL(ОплатыОбороты.СуммаОборот, 0) >= ДоговорыКонтрагентов.СуммаДоговора
Обратите внимание на использование ГДЕ вместо ИМЕЮЩИЕ. Поскольку виртуальная таблица Обороты уже сгруппирована по измерениям (в данном случае по договору), поле СуммаОборот не является агрегатом в контексте текущего запроса, и мы можем накладывать на него обычное условие фильтрации.
Для повышения читаемости кода и упрощения отладки, особенно в сложных отчетах, рекомендуется использовать временные таблицы (ВТ). Это позволяет разделить логику получения данных и логику их фильтрации.
Разберем по шагам создание такого запроса:
// Помещаем данные об оплатах во временную таблицу
ВЫБРАТЬ
ОплатыОбороты.Договор КАК Договор,
ОплатыОбороты.СуммаОборот КАК СуммаОплаты
ПОМЕСТИТЬ ВТ_Оплаты
ИЗ
РегистрНакопления.Оплаты.Обороты КАК ОплатыОбороты
;
// Основной запрос с фильтрацией
ВЫБРАТЬ
Договоры.Ссылка КАК Договор,
Договоры.СуммаДоговора КАК СуммаДоговора,
ЕСТЬNULL(ВТ_Оплаты.СуммаОплаты, 0) КАК СуммаОплаты
ИЗ
Справочник.Договоры КАК Договоры
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Оплаты КАК ВТ_Оплаты
ПО Договоры.Ссылка = ВТ_Оплаты.Договор
ГДЕ
ВТ_Оплаты.СуммаОплаты >= Договоры.СуммаДоговора
В данном примере мы использовали ВНУТРЕННЕЕ СОЕДИНЕНИЕ, так как нас интересуют только оплаченные договоры (если оплаты нет совсем, сумма оплат будет 0, и условие все равно отсеет такой договор, если его сумма больше нуля).
При написании подобных запросов необходимо учитывать несколько критических моментов, которые влияют на корректность результата:
ЛЕВОЕ СОЕДИНЕНИЕ, если по договору не было ни одной оплаты, поле суммы будет содержать NULL. Любое сравнение с NULL (например, 100 > NULL) возвращает Ложь. Всегда используйте функцию ЕСТЬNULL(Поле, 0), чтобы гарантировать корректное математическое сравнение.СУММА(ВЫБОР...).ГДЕ. Это позволяет серверу СУБД подготовить выборку гораздо быстрее.Договор в регистре обязательно должно быть индексировано. Если это измерение — индекс создается автоматически. Если это реквизит — убедитесь, что в метаданных стоит свойство "Индексировать".Проанализировав ситуацию, можно сделать вывод: наиболее современным и производительным способом решения задачи является использование виртуальной таблицы оборотов в сочетании с механизмом временных таблиц. Это делает ваш код понятным для других разработчиков и стабильным при росте базы данных.