Как получить остатки на каждую дату в запросе 1С: подробное руководство

Программист 1С v8.3 (Управляемые формы) Управленческий учет IT и автоматизация бизнеса
← На главную

В разработке на платформе 1С:Предприятие 8.3 часто возникает задача построения отчета, в котором остатки товаров или денежных средств должны быть отображены на каждый день выбранного периода, независимо от того, были ли в этот день совершены какие-либо операции (движения). Стандартная виртуальная таблица ОстаткиИОбороты при обычном обращении выдает данные только за те периоды, в которых зафиксированы движения. В этой статье мы подробно разберем, как обойти это ограничение и получить полную картину остатков по дням.

Анализ проблемы: почему стандартный запрос не подходит

Рассмотрим стандартную ситуацию: нам нужно получить остатки на складе за период с 1 по 10 число месяца. Если движения по складу были только 1-го и 5-го числа, то простой запрос к виртуальной таблице РегистрНакопления.ТоварыНаСкладах.ОстаткиИОбороты с периодичностью День вернет нам всего две строки. Для построения графиков или детальных отчетов для руководства этого недостаточно, так как требуются данные за все 10 дней.

Для решения этой задачи мы проанализируем два основных подхода: использование временной таблицы дат в запросе и настройку дополнения периодов в системе компоновки данных (СКД).

Решение №1: Использование временной таблицы дат в запросе

Этот метод считается классическим и наиболее гибким, так как он работает непосредственно в коде запроса и не зависит от настроек системы компоновки. Суть метода заключается в создании вспомогательного списка всех дат периода и последующем "присоединении" к ним данных об остатках.

Шаг 1: Формирование последовательности дат. Сначала нам необходимо получить таблицу, содержащую все даты интересующего нас интервала. В 1С нет встроенной функции запроса "ПолучитьТаблицуДат", поэтому мы воспользуемся хитростью с объединением данных из существующих таблиц (например, из производственного календаря или любого достаточно объемного справочника).

Проанализируем пример кода для создания такой таблицы:


// Создаем временную таблицу со всеми датами периода
ВЫБРАТЬ
    ДОБАВИТЬКДАТЕ(&ДатаНачала, ДЕНЬ, Номера.Номер - 1) КАК Период
ПОМЕСТИТЬ ВТ_Даты
ИЗ
    (ВЫБРАТЬ
        (т1.Номер + т10.Номер * 10 + т100.Номер * 100) КАК Номер
    ИЗ
        (ВЫБРАТЬ 0 КАК Номер ОБЪЕДИНИТЬ ВЫБРАТЬ 1 ОБЪЕДИНИТЬ ВЫБРАТЬ 2 ОБЪЕДИНИТЬ ВЫБРАТЬ 3 ОБЪЕДИНИТЬ ВЫБРАТЬ 4 ОБЪЕДИНИТЬ ВЫБРАТЬ 5 ОБЪЕДИНИТЬ ВЫБРАТЬ 6 ОБЪЕДИНИТЬ ВЫБРАТЬ 7 ОБЪЕДИНИТЬ ВЫБРАТЬ 8 ОБЪЕДИНИТЬ ВЫБРАТЬ 9) КАК т1
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ (ВЫБРАТЬ 0 КАК Номер ОБЪЕДИНИТЬ ВЫБРАТЬ 1 ОБЪЕДИНИТЬ ВЫБРАТЬ 2 ОБЪЕДИНИТЬ ВЫБРАТЬ 3 ОБЪЕДИНИТЬ ВЫБРАТЬ 4 ОБЪЕДИНИТЬ ВЫБРАТЬ 5 ОБЪЕДИНИТЬ ВЫБРАТЬ 6 ОБЪЕДИНИТЬ ВЫБРАТЬ 7 ОБЪЕДИНИТЬ ВЫБРАТЬ 8 ОБЪЕДИНИТЬ ВЫБРАТЬ 9) КАК т10 ПО ИСТИНА
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ (ВЫБРАТЬ 0 КАК Номер ОБЪЕДИНИТЬ ВЫБРАТЬ 1 ОБЪЕДИНИТЬ ВЫБРАТЬ 2 ОБЪЕДИНИТЬ ВЫБРАТЬ 3 ОБЪЕДИНИТЬ ВЫБРАТЬ 4 ОБЪЕДИНИТЬ ВЫБРАТЬ 5 ОБЪЕДИНИТЬ ВЫБРАТЬ 6 ОБЪЕДИНИТЬ ВЫБРАТЬ 7 ОБЪЕДИНИТЬ ВЫБРАТЬ 8 ОБЪЕДИНИТЬ ВЫБРАТЬ 9) КАК т100 ПО ИСТИНА
    ) КАК Номера
ГДЕ
    ДОБАВИТЬКДАТЕ(&ДатаНачала, ДЕНЬ, Номера.Номер - 1) <= &ДатаКонца
;

В данном примере мы создаем генератор чисел от 0 до 999 и прибавляем их к начальной дате. Это позволяет получить таблицу дат длиной почти в три года. Если период больше, можно добавить еще одно соединение.

Шаг 2: Соединение дат с виртуальной таблицей остатков. Теперь, когда у нас есть ВТ_Даты, мы выполним ЛЕВОЕ СОЕДИНЕНИЕ этой таблицы с данными регистра. Важно использовать именно левое соединение, чтобы сохранить все даты, даже если для них нет соответствующих записей в регистре.

Разберем структуру итогового запроса:


ВЫБРАТЬ
    ВТ_Даты.Период КАК Период,
    ЕСТЬNULL(ТоварыНаСкладах.Номенклатура, &ПустаяНоменклатура) КАК Номенклатура,
    ЕСТЬNULL(ТоварыНаСкладах.ВНаличииКонечныйОстаток, 0) КАК Остаток
ИЗ
    ВТ_Даты КАК ВТ_Даты
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.ОстаткиИОбороты(&ДатаНачала, &ДатаКонца, День, , ) КАК ТоварыНаСкладах
    ПО ВТ_Даты.Период >= ТоварыНаСкладах.Период

Важный нюанс: Простое соединение по равенству дат вернет остаток только на те дни, когда были движения. Чтобы получить остаток на "пустые" дни, нам нужно использовать логику получения последнего актуального значения. Однако в языке запросов 1С соединение по условию "больше или равно" может привести к значительному падению производительности на больших объемах данных. Поэтому чаще используется механизм СКД, который мы рассмотрим далее.

Решение №2: Использование СКД (Системы компоновки данных)

Разберем, как решить задачу наиболее эффективно средствами платформы. В СКД реализован встроенный механизм дополнения периодов, который автоматически создает недостающие строки в отчете.

Для настройки этого механизма выполним следующие шаги:

  1. Откроем схему компоновки данных и перейдем на закладку "Наборы данных".
  2. В запросе выберем виртуальную таблицу ОстаткиИОбороты с периодичностью День.
  3. На закладке "Роли" для поля Период нажмем на кнопку редактирования.
  4. В открывшемся окне установим флаг "Обязательное" и укажем тип периода. Самое важное — в поле "Роль" должно быть указано, что это поле периода.
  5. Перейдем в настройки отчета, на закладку "Детальные записи" или выберем конкретную группировку.
  6. В окне выбора полей группировки для поля Период перейдем в настройки и установим "Тип дополнения"День, а также укажим "Начало периода" и "Конец периода" (через параметры).

Проанализируем, что произойдет: СКД сама сгенерирует все даты в интервале и "растянет" остатки. Если движения не было, система подставит значение остатка из предыдущей доступной записи. Это самый производительный и правильный с точки зрения архитектуры 1С способ.

Решение №3: Использование функции "РассчитатьОстатки" в коде

Если нам нужно получить остатки программно без использования СКД и сложных запросов с соединениями, мы можем применить метод перебора в цикле, хотя он менее эффективен для больших отчетов.

Рассмотрим пример алгоритма:


ТекущаяДата = НачалоДня(Объект.ДатаНачала);
Пока ТекущаяДата <= Объект.ДатаКонца Цикл
    // Вызов метода для получения остатков на конкретную дату
    ОстаткиНаДату = РегистрыНакопления.ТоварыНаСкладах.Остатки(ТекущаяДата, Новый Структура("Склад", ВыбранныйСклад));
    
    Для Каждого СтрокаОстатков Из ОстаткиНаДату Цикл
        // Обработка данных
        Сообщить("Дата: " + ТекущаяДата + ", Товар: " + СтрокаОстатков.Номенклатура + ", Остаток: " + СтрокаОстатков.Количество);
    КонецЦикла;
    
    ТекущаяДата = ТекущаяДата + 86400; // Добавляем одни сутки
КонецЦикла;

Выясним причину, по которой этот метод используется редко: для каждой даты выполняется отдельный запрос к базе данных. Если период составляет год (365 дней), система сделает 365 обращений к SQL-серверу, что существенно замедлит работу программы. Рекомендуется использовать этот метод только для очень коротких интервалов или в фоновых заданиях, где скорость не критична.

Оптимизация и важные рекомендации

При работе с остатками на каждую дату следует придерживаться следующих правил:

Подводя итог, мы проанализировали три способа решения задачи. Для профессиональной разработки в 1С наиболее предпочтительным является использование механизмов дополнения периодов в СКД, так как это штатный инструмент платформы, оптимизированный для работы с СУБД.

← На главную