Как получить данные из регистра накопления без учета текущего документа?

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

В процессе разработки на платформе 1С:Предприятие часто возникает задача получения данных из регистра накопления (остатков или оборотов) таким образом, чтобы движения текущего документа не влияли на результат. Это особенно актуально, когда код выполняется в модуле формы или в процессе проведения уже записанного документа, движения которого уже «сидят» в базе данных. Если мы просто обратимся к виртуальной таблице остатков, мы получим актуальный итог с учетом текущего документа, что может привести к логическим ошибкам (например, при проверке превышения остатка).

В данной статье мы подробно разберем все способы решения этой задачи: от простых условий в запросе до профессиональных методов с использованием специальных объектов платформы.

Метод 1. Использование объекта Граница (наиболее эффективный способ)

Если нам необходимо получить остатки на момент времени «прямо перед документом», самым правильным и производительным способом будет использование типа данных Граница. Вместо того чтобы передавать в параметры виртуальной таблицы просто дату или момент времени, мы создадим специальный объект, который укажет системе, что движения самого регистратора включать не нужно.

Проанализируем пример кода, который можно использовать в модуле формы или объекта:


// Получаем момент времени текущего документа
ТекущийМомент = Объект.Ссылка.МоментВремени();

// Создаем границу, исключая движения самого документа
// ВидГраницы.Исключая — это ключевой параметр
ГраницаБезДокумента = Новый Граница(ТекущийМомент, ВидГраницы.Исключая);

Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ТоварыНаСкладахОстатки.Номенклатура КАК Номенклатура,
    |	ТоварыНаСкладахОстатки.ВНаличииОстаток КАК Остаток
    |ИЗ
    |	РегистрНакопления.ТоварыНаСкладах.Остатки(&Период, ) КАК ТоварыНаСкладахОстатки";

Запрос.УстановитьПараметр("Период", ГраницаБезДокумента);
РезультатЗапроса = Запрос.Выполнить();

Почему это работает лучше всего? Использование объекта Граница с параметром ВидГраницы.Исключая заставляет СУБД рассчитывать итоги ровно на ту микросекунду, которая предшествует записи данного документа. Система не выполняет лишних операций вычитания и эффективно использует индексы итогов.

Метод 2. Условие «Не равно Регистратор» в параметрах виртуальной таблицы

Иногда разработчики пытаются решить задачу «в лоб», добавляя условие в параметры виртуальной таблицы оборотов или остатков. Рассмотрим, как это выглядит в тексте запроса:


"ВЫБРАТЬ
|	ТоварыНаСкладахОбороты.Номенклатура,
|	ТоварыНаСкладахОбороты.КоличествоОборот
|ИЗ
|	РегистрНакопления.ТоварыНаСкладах.Обороты(&Начало, &Конец, , Регистратор <> &ТекДокумент) КАК ТоварыНаСкладахОбороты"

Рассмотрим подводные камни этого метода. С точки зрения производительности СУБД, оператор «не равно» (<>) является «несаргабельным» (non-sargable). Это означает, что индекс по полю Регистратор не сможет быть использован эффективно. Системе придется сканировать огромный объем данных, чтобы исключить одну конкретную запись. Если ваш регистр содержит миллионы записей, такой запрос со временем начнет серьезно тормозить систему.

Однако для виртуальной таблицы Обороты, если период очень узкий (например, одна секунда), такой метод допустим, хотя и не рекомендуется как основной.

Метод 3. Арифметическое вычитание движений текущего документа

Этот способ часто применяется, когда нам нужно получить остатки «до» документа, но мы по каким-то причинам не хотим использовать Границу. Идея заключается в том, чтобы получить текущие остатки (которые уже включают документ) и вычесть из них движения самого документа. Посмотрим на структуру такого запроса:


ВЫБРАТЬ
    ВТ.Номенклатура,
    СУММА(ВТ.Количество) КАК ОстатокЧистый
ИЗ (
    // Получаем текущие остатки (с учетом документа)
    ВЫБРАТЬ
        Т.Номенклатура КАК Номенклатура,
        Т.КоличествоОстаток КАК Количество
    ИЗ
        РегистрНакопления.ТоварыНаСкладах.Остатки(&Период, ) КАК Т
    
    ОБЪЕДИНИТЬ ВСЕ
    
    // Вычитаем движения нашего документа
    ВЫБРАТЬ
        Движения.Номенклатура,
        ВЫБОР 
            КОГДА Движения.ВидДвижения = ЗНАЧЕНИЕ(ВидДвиженияНакопления.Приход) 
            ТОГДА -Движения.Количество 
            ИНАЧЕ Движения.Количество 
        КОНЕЦ
    ИЗ
        РегистрНакопления.ТоварыНаСкладах КАК Движения
    ГДЕ
        Движения.Регистратор = &Регистратор
) КАК ВТ
СГРУППИРОВАТЬ ПО
    ВТ.Номенклатура

Проанализируем ситуацию: этот метод надежен, так как обращение к физической таблице по конкретному регистратору выполняется очень быстро (используется индекс). Мы берем текущий итог и «откатываем» его назад вручную. Это полезно, если вы работаете в сложной транзакции, где итоги могут быть еще не зафиксированы окончательно.

Метод 4. Особенности получения оборотов

Если автора интересуют именно обороты за период без учета текущего документа, подход немного меняется. Проанализируем два сценария:

  1. Документ находится внутри периода: Если мы получаем обороты за месяц, а наш документ проведен в середине этого месяца, то использование Границы не поможет (она работает только для срезов на точку времени). В этом случае единственный путь — использовать условие Регистратор <> &ТекДокумент в параметрах виртуальной таблицы оборотов.
  2. Документ — единственный за период: Если нам нужны обороты «до момента» этого документа, мы просто устанавливаем параметр КонецПериода в значение Граница(МоментВремени, ВидГраницы.Исключая).

Метод 5. Работа в транзакции и ДополнительныеСвойства

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

Опытные разработчики часто используют следующий прием:

Использование виртуальной таблицы ОстаткиИОбороты

Рассмотрим подробнее таблицу ОстаткиИОбороты. Она обладает уникальной гибкостью. Мы можем получить поле НачальныйОстаток, передав в НачалоПериода момент времени документа с видом границы Исключая. Это автоматически даст нам результат, не учитывающий движения текущего регистратора, даже если запрос выполняется «внутри» формы уже проведенного документа.

Резюмируем:

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

← На главную