В практике разработки на платформе 1С:Предприятие 8 часто возникает задача анализа активности контрагентов. Одной из типичных проблем является определение актуального (последнего использованного) счета поставщика. При этом логика часто усложняется дополнительными условиями: например, если счет не использовался более трех месяцев, он должен терять признак активности. Рассмотрим, как правильно составить такой запрос, избежав дублирования строк и лишней нагрузки на систему.
Разберем ситуацию: нам необходимо пометить поле Отметка значением ИСТИНА для того счета, который фигурировал в самом последнем документе ЗаказПоставщику. Если же документ был создан давно (более 3 месяцев назад), отметка должна быть ЛОЖЬ. Основная сложность, с которой сталкиваются разработчики, — это получение корректной выборки без дублей, когда один и тот же счет попадает и в категорию "последний", и в категорию "старый".
Проанализируем первую ошибку — использование конструкции ОБЪЕДИНИТЬ ВСЕ для разделения логики "последний" и "остальные". Это приводит к тому, что система дважды сканирует таблицу документов, что крайне неэффективно. Вместо этого мы будем использовать соединение с временной таблицей и конструкцию ВЫБОР КОГДА.
Первым делом нам необходимо выяснить, когда в последний раз каждый контрагент совершал заказ. Для этого мы создадим временную таблицу. Важно помнить, что если мы просто сгруппируем по дате, то при наличии нескольких заказов в одну и ту же секунду (что реально в многопользовательской среде), запрос может отработать неоднозначно.
Рассмотрим правильный код для подготовки временной таблицы:
ВЫБРАТЬ
ЗаказПоставщику.Контрагент КАК Контрагент,
МАКСИМУМ(ЗаказПоставщику.Дата) КАК МаксДата
ПОМЕСТИТЬ ВТ_ПоследниеДаты
ИЗ
Документ.ЗаказПоставщику КАК ЗаказПоставщику
ГДЕ
ЗаказПоставщику.Проведен = ИСТИНА
СГРУППИРОВАТЬ ПО
ЗаказПоставщику.Контрагент
ИНДЕКСИРОВАТЬ ПО
Контрагент
;
Обратите внимание на использование ИНДЕКСИРОВАТЬ ПО. Это критически важно для производительности при последующем соединении таблиц. Мы также добавили условие на проведенность документа, чтобы не учитывать черновики.
Теперь проанализируем, как связать полученные даты с конкретными счетами. Нам нужно получить срез последних данных. Выясним причину, по которой нельзя просто соединить таблицы по дате: у одного контрагента может быть несколько документов с одинаковой (максимальной) датой, но разными счетами. В таком случае рекомендуется использовать дополнительный фильтр по Ссылка, но для базовой задачи нам достаточно соединиться по паре Контрагент + Дата.
Разберем, как реализовать условие актуальности счета (не старше 3 месяцев) прямо в запросе, используя функцию ДОБАВИТЬКДАТЕ.
ВЫБРАТЬ
ЗаказПоставщику.Контрагент КАК Контрагент,
ЗаказПоставщику.СчетПоставщика КАК СчетПоставщика,
ВЫБОР
КОГДА ЗаказПоставщику.Дата = ВТ_ПоследниеДаты.МаксДата
И ЗаказПоставщику.Дата >= ДОБАВИТЬКДАТЕ(&ТекущаяДата, МЕСЯЦ, -3)
ТОГДА ИСТИНА
ИНАЧЕ ЛОЖЬ
КОНЕЦ КАК Отметка
ИЗ
Документ.ЗаказПоставщику КАК ЗаказПоставщику
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_ПоследниеДаты КАК ВТ_ПоследниеДаты
ПО ЗаказПоставщику.Контрагент = ВТ_ПоследниеДаты.Контрагент
И ЗаказПоставщику.Дата = ВТ_ПоследниеДаты.МаксДата
СГРУППИРОВАТЬ ПО
ЗаказПоставщику.Контрагент,
ЗаказПоставщику.СчетПоставщика,
ВЫБОР
КОГДА ЗаказПоставщику.Дата = ВТ_ПоследниеДаты.МаксДата
И ЗаказПоставщику.Дата >= ДОБАВИТЬКДАТЕ(&ТекущаяДата, МЕСЯЦ, -3)
ТОГДА ИСТИНА
ИНАЧЕ ЛОЖЬ
КОНЕЦ
В приведенном выше коде мы используем ВНУТРЕННЕЕ СОЕДИНЕНИЕ, что автоматически отсекает все старые записи, оставляя только те счета, которые использовались в "последнюю дату". Это избавляет нас от необходимости использовать ОБЪЕДИНИТЬ ВСЕ.
Посмотрим на важные нюансы, которые помогут сделать запрос профессиональным:
ГДЕ ЗаказПоставщику.СчетПоставщика <> ЗНАЧЕНИЕ(Справочник.БанковскиеСчета.ПустаяСсылка).Дата содержит время. Если необходимо найти счет, использованный "вчера" (весь день), используйте функции НАЧАЛОПЕРИОДА и КОНЕЦПЕРИОДА для параметров.&ТекущаяДата значение функции ТекущаяДатаСеанса(), а не серверное время.Проанализируем финальную структуру запроса, которая учитывает индексацию, агрегацию и логические условия в одном проходе по основной таблице:
// 1. Находим последнюю дату активности по каждому контрагенту
ВЫБРАТЬ
ЗаказПоставщику.Контрагент КАК Контрагент,
МАКСИМУМ(ЗаказПоставщику.Дата) КАК МаксДата
ПОМЕСТИТЬ ВТ_МаксДаты
ИЗ
Документ.ЗаказПоставщику КАК ЗаказПоставщику
ГДЕ
ЗаказПоставщику.Проведен
И ЗаказПоставщику.СчетПоставщика <> ЗНАЧЕНИЕ(Справочник.БанковскиеСчета.ПустаяСсылка)
СГРУППИРОВАТЬ ПО
ЗаказПоставщику.Контрагент
ИНДЕКСИРОВАТЬ ПО
Контрагент
;
// 2. Выбираем счета, соответствующие этим датам, с проверкой периода
ВЫБРАТЬ
Заказ.Контрагент КАК Контрагент,
Заказ.СчетПоставщика КАК СчетПоставщика,
ВЫБОР
КОГДА Заказ.Дата >= &ГраницаАктивности // Граница (ТекущаяДата - 3 месяца)
ТОГДА ИСТИНА
ИНАЧЕ ЛОЖЬ
КОНЕЦ КАК ЭтоПоследнийАктивныйСчет
ИЗ
Документ.ЗаказПоставщику КАК Заказ
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_МаксДаты КАК ПоследниеДаты
ПО Заказ.Контрагент = ПоследниеДаты.Контрагент
И Заказ.Дата = ПоследниеДаты.МаксДата
СГРУППИРОВАТЬ ПО
Заказ.Контрагент,
Заказ.СчетПоставщика,
Заказ.Дата
В данном примере мы вынесли расчет "3 месяца назад" в параметр &ГраницаАктивности на стороне встроенного языка (используя ДобавитьМесяц(ТекущаяДата(), -3)), что является более предпочтительным для производительности SQL-сервера, так как избавляет его от лишних вычислений в каждой строке выборки.
Важный момент: если ваша база данных содержит миллионы заказов, прямое обращение к таблице Документ.ЗаказПоставщику может замедлиться. В таком случае рассмотрите возможность получения данных из регистров накопления (например, Закупки), которые в архитектуре 1С работают значительно быстрее для получения последних оборотов.