В практике разработки на платформе 1С часто возникает задача: среди множества документов (например, оплат) найти самый первый или самый последний для конкретного объекта (заказа, договора или контрагента). На первый взгляд задача кажется тривиальной и решаемой через функцию МИНИМУМ, однако начинающие разработчики часто сталкиваются с тем, что запрос продолжает возвращать все документы вместо одного. В этой статье мы подробно разберем, почему это происходит, и как правильно составить запрос для получения корректного результата.
Рассмотрим ситуацию, с которой столкнулся автор темы. В системе есть Заказ клиента и несколько документов Безналичная оплата, привязанных к нему. Цель — вывести в отчет только самую раннюю оплату. Типичный ошибочный запрос выглядит следующим образом:
ВЫБРАТЬ
ДенежныеСредства.Заказ КАК Заказ,
ДенежныеСредства.ПлатежныйДокумент КАК ПлатежныйДокумент,
МИНИМУМ(ДенежныеСредства.ПлатежныйДокумент.Дата) КАК ДатаОплаты,
ДенежныеСредства.СуммаРегл КАК Сумма
ИЗ
РегистрНакопления.ДенежныеСредстваБезналичные КАК ДенежныеСредства
СГРУППИРОВАТЬ ПО
ДенежныеСредства.Заказ,
ДенежныеСредства.СуммаРегл,
ДенежныеСредства.ПлатежныйДокумент
Проанализируем, почему этот код не работает. Функция МИНИМУМ (или МАКСИМУМ) работает корректно только в рамках сформированных групп. Когда мы добавляем в секцию СГРУППИРОВАТЬ ПО поле ПлатежныйДокумент (ссылку на документ), каждая строка становится уникальной, так как ссылки у разных документов разные. В результате платформа 1С не может "схлопнуть" несколько документов оплаты в одну строку, и функция МИНИМУМ просто возвращает дату того же самого документа, который указан в этой строке. Мы получаем список всех оплат, что противоречит задаче.
Для решения задачи необходимо разделить получение минимальной даты и получение данных самого документа на два этапа. Разберем по шагам логику работающего решения:
Заказ) и минимальную дату (МИНИМУМ(Дата)). В этом запросе не должно быть никаких лишних полей, кроме тех, по которым мы ищем минимум.Рассмотрим пример кода, реализующий эту логику:
ВЫБРАТЬ
МинимальныеДаты.Заказ КАК Заказ,
МинимальныеДаты.ДатаОплаты КАК ДатаОплаты,
ВсеОплаты.ПлатежныйДокумент КАК ПлатежныйДокумент,
ВсеОплаты.СуммаРегл КАК СуммаРегл
ИЗ
(ВЫБРАТЬ
ДенежныеСредства.Заказ КАК Заказ,
МИНИМУМ(ДенежныеСредства.ПлатежныйДокумент.Дата) КАК ДатаОплаты
ИЗ
РегистрНакопления.ДенежныеСредстваБезналичные КАК ДенежныеСредства
СГРУППИРОВАТЬ ПО
ДенежныеСредства.Заказ) КАК МинимальныеДаты
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрНакопления.ДенежныеСредстваБезналичные КАК ВсеОплаты
ПО МинимальныеДаты.Заказ = ВсеОплаты.Заказ
И МинимальныеДаты.ДатаОплаты = ВсеОплаты.ПлатежныйДокумент.Дата
В этом примере первый подзапрос определяет "когда была первая оплата", а соединение находит "какой именно документ был в это время".
Проанализируем ситуацию, упомянутую в обсуждении: что если по одному заказу проведено две оплаты в одну и ту же секунду? В этом случае вышеуказанный запрос вернет две строки, так как условие ДатаОплаты = Дата сработает для обоих документов. Если нам критично получить ровно одну запись, алгоритм нужно усложнить еще на один шаг:
МИНИМУМ(Ссылка)) среди всех документов, которые имеют эту минимальную дату.Это гарантирует уникальность результата, так как ссылка в системе 1С всегда уникальна.
При работе с большими объемами данных в реальных конфигурациях (УТ 11, ERP 2.5) использование вложенных запросов может замедлять работу системы, так как СУБД сложнее построить оптимальный план выполнения. Профессиональным стандартом считается использование временных таблиц. Перепишем наше решение в пакетный запрос:
// Пакет 1: Получаем минимальные даты
ВЫБРАТЬ
ДенежныеСредства.Заказ КАК Заказ,
МИНИМУМ(ДенежныеСредства.ПлатежныйДокумент.Дата) КАК ДатаОплаты
ПОМЕСТИТЬ ВТ_МинимальныеДаты
ИЗ
РегистрНакопления.ДенежныеСредстваБезналичные КАК ДенежныеСредства
СГРУППИРОВАТЬ ПО
ДенежныеСредства.Заказ
;
// Пакет 2: Соединяем и получаем результат
ВЫБРАТЬ
ВТ_МинимальныеДаты.Заказ,
ВТ_МинимальныеДаты.ДатаОплаты,
ДенежныеСредства.ПлатежныйДокумент,
ДенежныеСредства.СуммаРегл
ИЗ
ВТ_МинимальныеДаты КАК ВТ_МинимальныеДаты
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрНакопления.ДенежныеСредстваБезналичные КАК ДенежныеСредства
ПО ВТ_МинимальныеДаты.Заказ = ДенежныеСредства.Заказ
И ВТ_МинимальныеДаты.ДатаОплаты = ДенежныеСредства.ПлатежныйДокумент.Дата
Такой подход работает быстрее, так как платформа создает индексы по полям временных таблиц, что значительно ускоряет операцию соединения.
При решении задачи именно в контексте Управления торговлей 11 стоит помнить о специфике регистров. Автор темы использует ДенежныеСредстваБезналичные. Однако, если задача стоит в анализе оплат по конкретным заказам, более правильным может быть обращение к регистру РасчетыСКлиентами или РасчетыСКлиентамиПоДокументам. Почему это важно?
Во-первых, в регистре денежных средств отражается движение по банковскому счету, но там не всегда есть привязка к заказу (например, если платеж был общим авансом). Во-вторых, в регистрах расчетов хранится информация о том, как программа распределила (зачла) оплату. Часто бывает, что платежка пришла вчера, а к заказу её привязали сегодня — в этом случае "первая оплата заказа" с точки зрения учета может отличаться от фактического поступления денег на расчетный счет.
Также не забывайте фильтровать записи. В регистрах накопления могут присутствовать движения с видом Расход (например, возврат денег клиенту). Если не указать условие на вид движения или тип хозяйственной операции, возврат может попасть в выборку как "первая оплата", что исказит отчетность.
Мы выяснили, что для получения минимального документа в запросе 1С нельзя использовать прямую группировку по ссылке документа. Решением является использование двухэтапной схемы с предварительным получением минимальной даты. Для повышения надежности рекомендуется использовать временные таблицы и дополнительную проверку на минимальную ссылку, чтобы исключить дублирование строк при совпадении времени документов до секунды.