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