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