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