В практике разработчика 1С часто возникает задача получения остатков регистров накопления не на фиксированную дату и не за простой линейный период, а на определенный, динамически формируемый список дат. Это могут быть даты проведения документов, даты изменения статусов объектов или, как в примере автора темы, даты из регистра сведений РегистрЗаписиИзменений.
В этой статье мы подробно разберем, как решить эту задачу в рамках запроса 1С, проанализируем проблемы производительности стандартных подходов и рассмотрим альтернативные, более эффективные методы реализации.
---
Рассмотрим алгоритм, предложенный в первом сообщении темы. Автор пытается получить остатки, используя декартово произведение (соединение без условий) таблицы дат и виртуальной таблицы ОстаткиИОбороты:
ВЫБРАТЬ РАЗЛИЧНЫЕ
КурсыВалют.Период КАК Период
ПОМЕСТИТЬ Дни
ИЗ
РегистрСведений.КурсыВалют КАК КурсыВалют
ГДЕ
КурсыВалют.Период МЕЖДУ &НачалоПериода И &КонецПериода
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
Движения.Номенклатура КАК Номенклатура,
Дни.Период КАК Период,
СУММА(ВЫБОР
КОГДА Движения.Период = &НачалоПериода
ТОГДА Движения.КоличествоКонечныйОстаток
ИНАЧЕ ВЫБОР
КОГДА Движения.Период <= Дни.Period
ТОГДА Движения.КоличествоОборот
ИНАЧЕ 0
КОНЕЦ
КОНЕЦ) КАК КоличествоКонечныйОстаток
ИЗ
Дни КАК Дни,
РегистрНакопления.ТоварыНаСкладах.ОстаткиИОбороты(&НачалоПериода, &КонецПериода, День, , ) КАК Движения
СГРУППИРОВАТЬ ПО
Движения.Номенклатура,
Дни.Период
В этом коде есть несколько критических проблем:
ИЗ Дни КАК Дни, РегистрНакопления... КАК Движения заставляет СУБД перемножать каждую запись из таблицы дат на каждую запись таблицы остатков и оборотов. Если у вас 100 товаров и 30 дат, СУБД обработает 3000 строк. Но если товаров 10 000, а дат — 100, количество обрабатываемых строк вырастет до 1 000 000. Это вызовет колоссальную нагрузку на СУБД и зависание системы.&НачалоПериода и &КонецПериода. Есть только набор дат во временной таблице, на которые нужно получить срез остатков.Движения.Период <= Дни.Период находится внутри агрегатной функции СУММА(ВЫБОР...), что мешает СУБД использовать индексы и заставляет выполнять сканирование всей выборки.---
Выясним, как исправить исходный запрос. В первую очередь нам необходимо определить границы периода (минимальную и максимальную даты) прямо из нашей временной таблицы, чтобы передать их в параметры виртуальной таблицы ОстаткиИОбороты. Это позволит СУБД не рассчитывать итоги за всю историю регистра.
Разберем рабочий и оптимизированный вариант по шагам:
Шаг 1. Получаем список нужных дат и определяем границы периода.
ВЫБРАТЬ
РегистрЗаписиИзменений.ДатаЗаписи КАК ДатаЗаписи
ПОМЕСТИТЬ втДни
ИЗ
РегистрСведений.РегистрЗаписиИзменений КАК РегистрЗаписиИзменений
ГДЕ
РегистрЗаписиИзменений.Объект = &Объект
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
МИНИМУМ(втДни.ДатаЗаписи) КАК НачалоПериода,
МАКСИМУМ(втДни.ДатаЗаписи) КАК КонецПериода
ПОМЕСТИТЬ втГраницыПериода
ИЗ
втДни КАК втДни
Шаг 2. Получаем остатки и обороты, ограничивая их выбранными границами.
Чтобы передать динамические границы в параметры виртуальной таблицы, мы можем использовать соединение с таблицей втГраницыПериода или рассчитать их предварительно в коде. Ниже представлен вариант с получением данных через левое соединение, где условие даты перенесено в связь таблиц (как советовали в Сообщении 5):
ВЫБРАТЬ
Движения.Номенклатура КАК Номенклатура,
Дни.ДатаЗаписи КАК Период,
СУММА(ВЫБОР
КОГДА Движения.Период = Границы.НачалоПериода
ТОГДА Движения.КоличествоКонечныйОстаток
ИНАЧЕ ВЫБОР
КОГДА Движения.Период <= Дни.ДатаЗаписи
ТОГДА Движения.КоличествоОборот
ИНАЧЕ 0
КОНЕЦ
КОНЕЦ) КАК КоличествоКонечныйОстаток
ИЗ
втДни КАК Дни
ВНУТРЕННЕЕ СОЕДИНЕНИЕ втГраницыПериода КАК Границы
ПО ИСТИНА
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.ОстаткиИОбороты(
&НачалоПериода,
&КонецПериода,
День, , ) КАК Движения
ПО Движения.Период <= Дни.ДатаЗаписи
СГРУППИРОВАТЬ ПО
Движения.Номенклатура,
Дни.ДатаЗаписи
Обратите внимание: Параметры &НачалоПериода и &КонецПериода в данном случае лучше предварительно получить из первого запроса к таблице дат в коде 1С и передать их в параметры основного запроса явным образом. Это гарантирует, что СУБД построит правильный план запроса.
---
Если ваши даты в списке сильно разрежены (например, вам нужны остатки на 5 дат с интервалом в полгода), использование виртуальной таблицы ОстаткиИОбороты заставит систему рассчитывать обороты за все эти полгода. В таком случае гораздо быстрее обратиться напрямую к физической таблице движений регистра.
Рассмотрим пример запроса, рассчитывающего остаток на каждую дату нарастающим итогом через таблицу движений:
ВЫБРАТЬ
Дни.ДатаЗаписи КАК Период,
Движения.Номенклатура КАК Номенклатура,
СУММА(ВЫБОР
КОГДА Движения.ВидДвижения = ЗНАЧЕНИЕ(ВидДвиженияНакопления.Приход)
ТОГДА Движения.Количество
ИНАЧЕ -Движения.Количество
КОНЕЦ) КАК Остаток
ИЗ
втДни КАК Дни
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах КАК Движения
ПО Движения.Период <= Дни.ДатаЗаписи
СГРУППИРОВАТЬ ПО
Дни.ДатаЗаписи,
Движения.Номенклатура
Преимущества этого метода:
Период и Номенклатура.---
Если вы разрабатываете отчет или можете позволить себе программно вызвать СКД и выгрузить результат в Таблицу Значений, то использование встроенного механизма связей наборов данных — это самый простой, быстрый и профессиональный способ решения проблемы.
Проанализируем пошагово, как настроить такую схему:
НаборДанныхДаты), который выбирает список ваших дат и объектов (например, Номенклатуру):
ВЫБРАТЬ
РегистрЗаписиИзменений.ДатаЗаписи КАК Период,
РегистрЗаписиИзменений.Номенклатура КАК Номенклатура
ИЗ
РегистрСведений.РегистрЗаписиИзменений КАК РегистрЗаписиИзменений
НаборДанныхОстатки), который получает остатки на одну конкретную дату:
ВЫБРАТЬ
Товары.Номенклатура КАК Номенклатура,
Товары.КоличествоОстаток КАК КоличествоОстаток
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(&ПериодГраница, Номенклатура = &Номенклатура) КАК Товары
НаборДанныхДатыНаборДанныхОстаткиНоменклатура = НоменклатураПериодГраница, Выражение источника — Период.При таком подходе платформа 1С автоматически оптимизирует выполнение запросов на уровне СУБД. Она сформирует пакетные запросы к остаткам конкретно под каждую дату из вашей таблицы, исключая избыточные расчеты оборотов.
---
При работе с остатками на список дат всегда придерживайтесь следующих правил:
Номенклатура В (ВЫБРАТЬ Номенклатура ИЗ втТовары).ИНДЕКСИРОВАТЬ ПО.Остатки, и объединяет результаты в одну таблицу значений.