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