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

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

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

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

Причина возникновения ошибки «Преобразование значения к типу Дата»

Проанализируем ситуацию, описанную пользователем. При обходе результата запроса в цикле код "валится" на строке ДатаД = СтрокаТаб.Дата;. Выясним причину этого поведения. Когда мы используем виртуальную таблицу ОстаткиИОбороты и устанавливаем периодичность Регистратор, система формирует результирующий набор данных, в который входят:

  1. Строки самих движений (приход/расход) — здесь поле Регистратор заполнено ссылкой на документ.
  2. Строки начального остатка — здесь поле Регистратор всегда равно NULL.
  3. Строки конечного остатка — здесь поле Регистратор также равно NULL.

Попытка обратиться к свойству Дата у значения NULL (через точку в запросе или в коде 1С) возвращает NULL. При попытке присвоить это значение переменной с типом Дата или выполнить над ним операции, требующие типизации, платформа выбрасывает исключение. Рассмотрим, как этого избежать в самом запросе.

Решение 1: Использование функции ЕСТЬNULL

Самый простой способ предотвратить падение кода — обеспечить наличие пустой даты вместо NULL. Для этого в тексте запроса следует использовать функцию ЕСТЬNULL. Проанализируем пример изменения поля в запросе:


ВЫБРАТЬ
    БланкиЗБ_.Регистратор КАК Регистратор,
    ЕСТЬNULL(БланкиЗБ_.Регистратор.Дата, ДАТАВРЕМЯ(1, 1, 1)) КАК ДатаРегистратора
ИЗ
    РегистрНакопления.ЛМБ_БланкиЗалоговыхБилетов.ОстаткиИОбороты(&ДатаНачала, &ДатаОкончания, Регистратор, , Филиал = &Филиал) КАК БланкиЗБ_

В этом случае, даже если регистратора нет (строка остатка), в колонку ДатаРегистратора попадет пустая дата "01.01.0001", что позволит коду работать без ошибок. Однако это не решает главную задачу — получение реальной даты последнего движения, если оно было до &ДатаНачала.

Решение 2: Соединение с основной таблицей для получения последней даты

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

Шаг 1. Получаем остатки за период во временную таблицу.

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

Шаг 3. Соединяем результаты.

Разберем пример кода запроса:


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

////////////////////////////////////////////////////////////////////////////////
// 2. Получаем последние даты движений по этим билетам (без привязки к &ДатаНачала)
ВЫБРАТЬ
    Регистр.ЗалоговыйБилет КАК ЗалоговыйБилет,
    МАКСИМУМ(Регистр.Период) КАК ДатаПоследнегоДвижения
ПОМЕСТИТЬ ВТ_ПоследниеДаты
ИЗ
    РегистрНакопления.ЛМБ_БланкиЗалоговыхБилетов КАК Регистр
ГДЕ
    Регистр.ЗалоговыйБилет В (ВЫБРАТЬ ЗалоговыйБилет ИЗ ВТ_ДанныеПериода)
    И Регистр.Период <= &ДатаОкончания
СГРУППИРОВАТЬ ПО
    Регистр.ЗалоговыйБилет;

////////////////////////////////////////////////////////////////////////////////
// 3. Соединяем всё вместе
ВЫБРАТЬ
    Данные.ЗалоговыйБилет,
    Данные.Остаток,
    Даты.ДатаПоследнегоДвижения
ИЗ
    ВТ_ДанныеПериода КАК Данные
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ПоследниеДаты КАК Даты
    ПО Данные.ЗалоговыйБилет = Даты.ЗалоговыйБилет

Важный момент: В физической таблице регистра накопления поле Период проиндексировано вместе с измерениями. Обращение к МАКСИМУМ(Период) с фильтром по измерению работает крайне быстро даже на миллионах записей, так как СУБД использует индекс.

Решение 3: Получение ссылки на последний регистратор через МоментВремени

В некоторых случаях недостаточно знать просто дату. Если в одну секунду было несколько движений, "последним" будет считаться документ с большим Моментом времени. Однако в языке запросов нельзя использовать МАКСИМУМ напрямую к полю МоментВремени. Проанализируем, как решить эту задачу правильно.

Для получения именно регистратора нам придется сначала найти максимальный период, а затем — максимальную ссылку внутри этого периода. Посмотрим на пример реализации:


ВЫБРАТЬ
    Регистр.ЗалоговыйБилет,
    МАКСИМУМ(Регистр.МоментВремени) КАК МаксМомент
ПОМЕСТИТЬ ВТ_Моменты
ИЗ
    РегистрНакопления.ЛМБ_БланкиЗалоговыхБилетов КАК Регистр
СГРУППИРОВАТЬ ПО
    Регистр.ЗалоговыйБилет;

ВЫБРАТЬ
    ВТ.ЗалоговыйБилет,
    Регистр.Регистратор,
    Регистр.Регистратор.Дата КАК ДатаРегистратора
ИЗ
    ВТ_Моменты КАК ВТ
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрНакопления.ЛМБ_БланкиЗалоговыхБилетов КАК Регистр
    ПО ВТ.ЗалоговыйБилет = Регистр.ЗалоговыйБилет
    И ВТ.МаксМомент = Регистр.МоментВремени

Обратите внимание, что использование конструкции Регистратор.Дата (обращение через точку) приводит к неявному LEFT JOIN с таблицами всех документов, которые могут быть регистраторами. Если в регистре десятки типов документов, такой запрос может работать медленно. Если поле Период в вашем регистре всегда совпадает с датой документа, лучше использовать МАКСИМУМ(Период) — это значительно сэкономит ресурсы сервера.

Рекомендации по оптимизации

При работе с регистрами накопления и получении дат регистраторов вне периода, следуйте этим правилам:

Таким образом, мы выяснили, что проблема автора была вызвана архитектурной особенностью виртуальных таблиц 1С. Комбинируя данные виртуальной таблицы за период и агрегатные функции физической таблицы регистра, можно получить полную картину движений объектов, включая их исторические данные.

← На главную