Как в запросе 1С отобрать договоры, сумма оплат по которым больше или равна сумме договора?

Программист 1С v8.3 (Управляемые формы) Управленческий учет
← На главную

При разработке отчетов или механизмов контроля взаиморасчетов часто возникает задача: отобрать только те объекты (например, договоры), которые удовлетворяют определенному финансовому условию, основанному на данных из разных источников. В нашем случае необходимо сравнить статичное значение из справочника — СуммаДоговора — с динамически вычисляемой суммой оплат из регистра накопления.

В этой статье мы подробно разберем несколько способов реализации такого условия в запросе, проанализируем ошибки начинающих разработчиков и выясним, какой метод является наиболее оптимальным с точки зрения производительности платформы 1С.

Способ 1. Использование секции ИМЕЮЩИЕ (HAVING)

Самый прямой путь решения задачи в рамках одного запроса — использование предложения ИМЕЮЩИЕ. Эта секция в языке запросов 1С предназначена специально для наложения условий на результат агрегатных функций (таких как СУММА, КОЛИЧЕСТВО, МАКСИМУМ).

Разберем важный нюанс: в секции ИМЕЮЩИЕ нельзя использовать псевдонимы полей, которые вы задали в секции ВЫБРАТЬ. Платформа 1С (как и стандартный SQL) требует повторения всего агрегатного выражения.

Рассмотрим пример кода, который решает поставленную задачу:


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

Проанализируем этот запрос. Мы используем ЛЕВОЕ СОЕДИНЕНИЕ, чтобы не потерять договоры, но основная фильтрация происходит в конце. Если сумма по всем записям регистра меньше суммы, указанной в справочнике, такая строка просто не попадет в итоговую выборку.

Способ 2. Использование виртуальных таблиц (Оптимальный метод)

Профессиональная разработка на 1С подразумевает отказ от прямого обращения к физическим таблицам регистров накопления, если можно использовать виртуальные таблицы. Прямое обращение к таблице РегистрНакопления.Оплаты заставляет систему сканировать все записи, что крайне неэффективно при больших объемах данных.

Выясним причину, почему виртуальная таблица Обороты лучше: она уже содержит предварительно рассчитанные итоги. Использование этой таблицы значительно ускоряет выполнение запроса.

Рассмотрим правильную структуру запроса:


ВЫБРАТЬ
    ДоговорыКонтрагентов.Ссылка КАК Договор,
    ДоговорыКонтрагентов.СуммаДоговора КАК СуммаДоговора,
    ЕСТЬNULL(ОплатыОбороты.СуммаОборот, 0) КАК Оплачено
ИЗ
    Справочник.ДоговорыКонтрагентов КАК ДоговорыКонтрагентов
        ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.Оплаты.Обороты(,,,) КАК ОплатыОбороты
        ПО ОплатыОбороты.Договор = ДоговорыКонтрагентов.Ссылка
ГДЕ
    ЕСТЬNULL(ОплатыОбороты.СуммаОборот, 0) >= ДоговорыКонтрагентов.СуммаДоговора

Обратите внимание на использование ГДЕ вместо ИМЕЮЩИЕ. Поскольку виртуальная таблица Обороты уже сгруппирована по измерениям (в данном случае по договору), поле СуммаОборот не является агрегатом в контексте текущего запроса, и мы можем накладывать на него обычное условие фильтрации.

Способ 3. Вложенные запросы и временные таблицы

Для повышения читаемости кода и упрощения отладки, особенно в сложных отчетах, рекомендуется использовать временные таблицы (ВТ). Это позволяет разделить логику получения данных и логику их фильтрации.

Разберем по шагам создание такого запроса:

  1. Сначала выберем все обороты по оплатам во временную таблицу.
  2. Затем соединим полученную таблицу со справочником и наложим финальное условие.

// Помещаем данные об оплатах во временную таблицу
ВЫБРАТЬ
    ОплатыОбороты.Договор КАК Договор,
    ОплатыОбороты.СуммаОборот КАК СуммаОплаты
ПОМЕСТИТЬ ВТ_Оплаты
ИЗ
    РегистрНакопления.Оплаты.Обороты КАК ОплатыОбороты
;

// Основной запрос с фильтрацией
ВЫБРАТЬ
    Договоры.Ссылка КАК Договор,
    Договоры.СуммаДоговора КАК СуммаДоговора,
    ЕСТЬNULL(ВТ_Оплаты.СуммаОплаты, 0) КАК СуммаОплаты
ИЗ
    Справочник.Договоры КАК Договоры
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Оплаты КАК ВТ_Оплаты
        ПО Договоры.Ссылка = ВТ_Оплаты.Договор
ГДЕ
    ВТ_Оплаты.СуммаОплаты >= Договоры.СуммаДоговора

В данном примере мы использовали ВНУТРЕННЕЕ СОЕДИНЕНИЕ, так как нас интересуют только оплаченные договоры (если оплаты нет совсем, сумма оплат будет 0, и условие все равно отсеет такой договор, если его сумма больше нуля).

Важные технические нюансы

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

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

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

← На главную