Как в 1С получить срез последних с последними заполненными значениями по каждому полю

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

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

Разбор проблемы на примере

Представим ситуацию, описанную автором темы. Есть регистр с тремя ресурсами: Поле_А (число), Поле_Б (число) и Поле_В (строка). Записи в регистре выглядят так:

  1. Период 01.01: А=1, Б=0, В=""
  2. Период 02.01: А=0, Б=5, В=""
  3. Период 03.01: А=0, Б=0, В="Текст"

Стандартный СрезПоследних на 04.01 вернет нам запись [0, 0, "Текст"]. Но бизнес-логика часто требует получить актуальное состояние объекта, то есть [1, 5, "Текст"]. Проанализируем способы решения этой задачи.

Способ 1. Использование нескольких виртуальных таблиц

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

Мы создаем запрос, в котором выбираем основную таблицу (например, справочник объектов) и последовательно присоединяем к ней виртуальные таблицы СрезПоследних. В параметрах каждой виртуальной таблицы мы прописываем условие отбора, чтобы исключить пустые значения.

Пример логики условий в параметрах ВТ:

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

Способ 2. Универсальный запрос через временные таблицы (Unpivot и Pivot)

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

Шаг 1. Сбор всех заполненных данных в одну временную таблицу. Мы «разворачиваем» колонки в строки. Это позволяет нам работать с каждым значением индивидуально.


ВЫБРАТЬ
    Регистр.Объект КАК Объект,
    "Поле_А" КАК ИмяКолонки,
    Регистр.Период КАК Период,
    Регистр.Поле_А КАК Значение
ПОМЕСТИТЬ втДанные
ИЗ
    РегистрСведений.ИмяРегистра КАК Регистр
ГДЕ
    Регистр.Поле_А <> 0

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
    Регистр.Объект,
    "Поле_Б",
    Регистр.Период,
    Регистр.Поле_Б
ИЗ
    РегистрСведений.ИмяРегистра КАК Регистр
ГДЕ
    Регистр.Поле_Б <> 0

Шаг 2. Поиск максимального периода для каждой колонки. Теперь нам нужно найти дату последней активности для каждого ресурса.


ВЫБРАТЬ
    втДанные.Объект,
    втДанные.ИмяКолонки,
    МАКСИМУМ(втДанные.Период) КАК МаксПериод
ПОМЕСТИТЬ втПоследниеДаты
ИЗ
    втДанные КАК втДанные
СГРУППИРОВАТЬ ПО
    втДанные.Объект,
    втДанные.ИмяКолонки

Шаг 3. Сборка итоговой строки. Мы соединяем полученные даты с исходными данными и «сворачиваем» их обратно в одну строку с помощью группировки и функции МАКСИМУМ.


ВЫБРАТЬ
    Результат.Объект,
    МАКСИМУМ(ВЫБОР КОГДА Результат.ИмяКолонки = "Поле_А" ТОГДА Результат.Значение ИНАЧЕ 0 КОНЕЦ) КАК Поле_А,
    МАКСИМУМ(ВЫБОР КОГДА Результат.ИмяКолонки = "Поле_Б" ТОГДА Результат.Значение ИНАЧЕ 0 КОНЕЦ) КАК Поле_Б
ИЗ
    (ВЫБРАТЬ
        втДанные.Объект,
        втДанные.ИмяКолонки,
        втДанные.Значение
    ИЗ
        втПоследниеДаты КАК Даты
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ втДанные КАК втДанные
        ПО Даты.Объект = втДанные.Объект 
        И Даты.ИмяКолонки = втДанные.ИмяКолонки 
        И Даты.МаксПериод = втДанные.Период) КАК Результат
СГРУППИРОВАТЬ ПО
    Результат.Объект

Способ 3. Оптимизация через «Условный Максимум»

Если мы хотим избежать использования ОБЪЕДИНИТЬ ВСЕ, мы можем применить методику условного агрегирования. Выясним причину, почему это эффективно: мы сканируем основную таблицу регистра только один раз.

Рассмотрим пример формирования координат:


ВЫБРАТЬ
    Объект,
    МАКСИМУМ(ВЫБОР КОГДА Поле_А <> 0 ТОГДА Период ИНАЧЕ ДАТАВРЕМЯ(1,1,1) КОНЕЦ) КАК ПериодА,
    МАКСИМУМ(ВЫБОР КОГДА Поле_Б <> 0 ТОГДА Период ИНАЧЕ ДАТАВРЕМЯ(1,1,1) КОНЕЦ) КАК ПериодБ
ПОМЕСТИТЬ втКоординаты
ИЗ
    РегистрСведений.ИмяРегистра
СГРУППИРОВАТЬ ПО
    Объект

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

Архитектурные рекомендации: как избежать проблемы

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

  1. Заполнение при записи (Proactive filling). Рассмотрим этот метод как основной для высоконагруженных систем. В модуле набора записей регистра в событии ПередЗаписью мы считываем текущий СрезПоследних. Если в записываемой строке есть пустые ресурсы, мы принудительно заполняем их данными из предыдущего среза. Таким образом, в регистре всегда хранятся «полные» слепки состояния, и обычный запрос СрезПоследних работает мгновенно и корректно.
  2. Переход к модели EAV (Entity-Attribute-Value). Разберем совет из сообщения 6. Вместо того чтобы плодить ресурсы (Поле А, Поле Б, Поле В), мы создаем одно измерение Свойство (ПланВидовХарактеристик) и один ресурс Значение. В этом случае каждое изменение любого поля — это отдельная строка. Стандартный срез последних по объекту и свойству всегда будет возвращать актуальное значение именно этого свойства.

Итоги

Выбор метода зависит от ваших условий:

← На главную