Задача: дебиторская задолженность
Разбираем классический вопрос с собеседования: как одним запросом получить задолженность контрагентов на нужную дату и оставить только должников.
Дебиторская задолженность — это деньги, которые контрагенты должны нашей компании: отгрузили товар или оказали услугу, а оплату ещё не получили. В 1С такие суммы обычно хранятся в регистре накопления «Взаиморасчёты с контрагентами».
Почти на каждом собеседовании 1С-разработчика звучит просьба: «Напишите запрос, который покажет, сколько нам должны контрагенты на первое число месяца». Задача выглядит простой, но проверяет сразу несколько важных навыков: понимаете ли вы регистры накопления, умеете ли работать с виртуальными таблицами и знаете ли, чем отбор через ИМЕЮЩИЕ отличается от отбора через ГДЕ. Разберём её по шагам.
Что вообще такое «долг на дату»
Взаиморасчёты — это регистр накопления вида «Остатки». Каждая отгрузка увеличивает долг контрагента (движение «Приход»), каждая оплата — уменьшает его (движение «Расход»). Долг на конкретный момент — это накопленный итог всех движений от начала времён до этой даты. Считать его вручную в цикле, перебирая документы, — плохая идея: медленно и легко ошибиться. Платформа умеет отдавать готовый остаток через виртуальную таблицу — специальную таблицу, которую 1С рассчитывает «на лету», не храня физически.
У регистра накопления «Остатки» такая таблица называется Остатки. Обращаемся к ней так: РегистрНакопления.ВзаиморасчётыСКонтрагентами.Остатки(&ДатаОтчёта). Первый параметр в скобках — Период: на какой момент нам нужен остаток. Именно этот параметр отличает «сколько должны прямо сейчас» от «сколько были должны на 1 января».
Шаг 1. Простой запрос остатков
Начнём с минимального рабочего запроса — просто вытащим остатки по всем контрагентам на дату:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Взаиморасчёты.Контрагент КАК Контрагент,
| Взаиморасчёты.СуммаОстаток КАК Долг
|ИЗ
| РегистрНакопления.ВзаиморасчётыСКонтрагентами.Остатки(&ДатаОтчёта) КАК Взаиморасчёты";
Запрос.УстановитьПараметр("ДатаОтчёта", КонецДня(ДатаОтчёта));
Результат = Запрос.Выполнить().Выбрать();
Пока Результат.Следующий() Цикл
Сообщить(Результат.Контрагент.Наименование + ": " + Результат.Долг);
КонецЦикла;
Обратите внимание на три вещи. Во-первых, поле в виртуальной таблице «Остатки» называется не Сумма, а СуммаОстаток — платформа добавляет суффикс Остаток к каждому ресурсу регистра. Во-вторых, дату мы передаём параметром &ДатаОтчёта, а не вставляем в текст запроса — так безопаснее и быстрее. В-третьих, мы обернули дату в КонецДня(): без этого «01.02.2026» означает начало суток (00:00:00), и документы, проведённые в течение самого дня, в остаток не попадут.
Шаг 2. Оставляем только должников
Запрос выше вернёт всех контрагентов, у кого есть хоть какое-то движение. Но нас просили показать должников — тех, у кого долг больше нуля. Первая мысль новичка — дописать ГДЕ Взаиморасчёты.СуммаОстаток > 0. И для этого запроса такой вариант действительно сработает. Но на собеседовании любят усложнять: «А если по одному контрагенту несколько договоров, и нужно суммировать долг по всем договорам, а потом отобрать?» Вот тут появляется группировка — и вместе с ней ИМЕЮЩИЕ.
ВЫБРАТЬ
Взаиморасчёты.Контрагент КАК Контрагент,
СУММА(Взаиморасчёты.СуммаОстаток) КАК Долг
ИЗ
РегистрНакопления.ВзаиморасчётыСКонтрагентами.Остатки(&ДатаОтчёта) КАК Взаиморасчёты
СГРУППИРОВАТЬ ПО
Взаиморасчёты.Контрагент
ИМЕЮЩИЕ
СУММА(Взаиморасчёты.СуммаОстаток) > 0
УПОРЯДОЧИТЬ ПО
Долг УБЫВ
Здесь мы свернули строки по контрагенту через СГРУППИРОВАТЬ ПО, сложили долги по всем его договорам через СУММА(...), а затем оставили только тех, у кого итоговая сумма положительна. Отбор по агрегату — это и есть работа для ИМЕЮЩИЕ. В конце отсортировали по убыванию, чтобы самые крупные должники были сверху — так отчёт читается удобнее.
Результат:
ООО «Ромашка» 148 500,00
ИП Петров 52 300,00
ООО «Вектор» 9 800,00
Как это работает
Ключевая идея — понять порядок, в котором платформа выполняет части запроса. Сначала берётся источник данных (виртуальная таблица Остатки — платформа рассчитывает остатки на переданную дату). Потом строки группируются по контрагенту. Затем для каждой группы вычисляются агрегаты — СУММА. И только после этого применяется ИМЕЮЩИЕ: оно фильтрует уже готовые сгруппированные строки. Поэтому в ИМЕЮЩИЕ можно писать СУММА(...) > 0, а в ГДЕ — нельзя: на момент ГДЕ суммы ещё не посчитаны.
Правило простое: ГДЕ фильтрует отдельные строки до группировки, ИМЕЮЩИЕ — группы после неё. Если условие можно проверить на исходной строке (например, «только по конкретной организации»), ставьте его в параметры виртуальной таблицы или в ГДЕ — это быстрее. Если условие про итог группы («сумма долга больше нуля») — только ИМЕЮЩИЕ.
// Условие по организации — до группировки, в параметры ВТ (быстро):
РегистрНакопления.ВзаиморасчётыСКонтрагентами.Остатки(
&ДатаОтчёта,
Организация = &Организация) КАК Взаиморасчёты
// ...
ИМЕЮЩИЕ
СУММА(Взаиморасчёты.СуммаОстаток) > 0 // условие по итогу — после группировки
Частые ошибки
Забыли КонецДня() у даты. Передали в параметр ТекущаяДата() или дату без времени — и «потеряли» все документы за сегодняшний день. Долг оказывается меньше реального. Всегда явно решайте, включён ли последний день в отчёт, и оборачивайте границу в КонецДня().
Пишут СуммаОстаток как Сумма. Поле в виртуальной таблице «Остатки» — с суффиксом. Если написать просто Сумма, платформа выдаст ошибку «поле не найдено». В таблице «Обороты» суффикс другой — СуммаОборот. Всегда сверяйтесь с составом виртуальной таблицы в конструкторе запроса.
Тянут отбор должников в ГДЕ при группировке. Условие ГДЕ СУММА(...) > 0 просто не скомпилируется — агрегатов в ГДЕ быть не может. А отбор по сырому СуммаОстаток в ГДЕ при наличии нескольких договоров даст неверный результат: отфильтруются отдельные строки договоров, а не итоговый долг контрагента.
Считают долг перебором документов в цикле. Медленно, тяжело для базы и почти всегда с ошибками в логике «приход минус расход». Регистр остатков и виртуальная таблица Остатки существуют именно для того, чтобы этого не делать.
Итоги
- Долг на дату берём из виртуальной таблицы
Остаткирегистра накопления, дату передаём параметром&ДатаОтчётав её первый аргумент (Период). - Ресурс в «Остатки» называется с суффиксом —
СуммаОстаток, а неСумма. - Границу периода оборачиваем в
КонецДня(), чтобы не потерять документы за последний день. - Отбор «только должники» по сумме нескольких договоров делаем через
СГРУППИРОВАТЬ ПО+ИМЕЮЩИЕ СУММА(...) > 0. - Помним порядок:
ГДЕ— до группировки (по строкам),ИМЕЮЩИЕ— после (по группам).