При работе с периодическими регистрами сведений программисты часто сталкиваются со стандартным поведением виртуальной таблицы СрезПоследних. Она возвращает последнюю запись целиком на указанную дату. Однако в реальности данные могут вноситься «вразнобой»: в одной записи заполнено поле А, в следующей — только поле Б, а в третьей — только поле В. Если мы просто обратимся к срезу последних, то получим значения из самой свежей записи, где большинство полей могут быть пустыми. Наша задача — собрать «пазл» из последних ненулевых (непустых) значений для каждого ресурса отдельно.
Представим ситуацию, описанную автором темы. Есть регистр с тремя ресурсами: Поле_А (число), Поле_Б (число) и Поле_В (строка). Записи в регистре выглядят так:
Стандартный СрезПоследних на 04.01 вернет нам запись [0, 0, "Текст"]. Но бизнес-логика часто требует получить актуальное состояние объекта, то есть [1, 5, "Текст"]. Проанализируем способы решения этой задачи.
Рассмотрим самый простой и очевидный вариант: если нам не нравится, как работает один срез последних, мы можем использовать несколько. Для каждого ресурса мы создадим свое соединение. Разберем этот алгоритм подробнее:
Мы создаем запрос, в котором выбираем основную таблицу (например, справочник объектов) и последовательно присоединяем к ней виртуальные таблицы СрезПоследних. В параметрах каждой виртуальной таблицы мы прописываем условие отбора, чтобы исключить пустые значения.
Пример логики условий в параметрах ВТ:
ГДЕ Поле_А <> 0ГДЕ Поле_Б <> 0ГДЕ Поле_В <> ""Важное уточнение: если во всех записях регистра по конкретному объекту поле всегда было пустым, левое соединение вернет NULL, что легко обрабатывается функцией ЕСТЬNULL. Этот метод прост в реализации, но при большом количестве полей (ресурсов) запрос становится громоздким и может снижать производительность.
Проанализируем более гибкий подход, который позволяет собрать данные за один проход по физической таблице регистра, не плодя десятки виртуальных таблиц. Рассмотрим по шагам пример кода, предложенный участниками обсуждения.
Шаг 1. Сбор всех заполненных данных в одну временную таблицу. Мы «разворачиваем» колонки в строки. Это позволяет нам работать с каждым значением индивидуально.
ВЫБРАТЬ
Регистр.Объект КАК Объект,
"Поле_А" КАК ИмяКолонки,
Регистр.Период КАК Период,
Регистр.Поле_А КАК Значение
ПОМЕСТИТЬ втДанные
ИЗ
РегистрСведений.ИмяРегистра КАК Регистр
ГДЕ
Регистр.Поле_А <> 0
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
Регистр.Объект,
"Поле_Б",
Регистр.Период,
Регистр.Поле_Б
ИЗ
РегистрСведений.ИмяРегистра КАК Регистр
ГДЕ
Регистр.Поле_Б <> 0
Шаг 2. Поиск максимального периода для каждой колонки. Теперь нам нужно найти дату последней активности для каждого ресурса.
ВЫБРАТЬ
втДанные.Объект,
втДанные.ИмяКолонки,
МАКСИМУМ(втДанные.Период) КАК МаксПериод
ПОМЕСТИТЬ втПоследниеДаты
ИЗ
втДанные КАК втДанные
СГРУППИРОВАТЬ ПО
втДанные.Объект,
втДанные.ИмяКолонки
Шаг 3. Сборка итоговой строки. Мы соединяем полученные даты с исходными данными и «сворачиваем» их обратно в одну строку с помощью группировки и функции МАКСИМУМ.
ВЫБРАТЬ
Результат.Объект,
МАКСИМУМ(ВЫБОР КОГДА Результат.ИмяКолонки = "Поле_А" ТОГДА Результат.Значение ИНАЧЕ 0 КОНЕЦ) КАК Поле_А,
МАКСИМУМ(ВЫБОР КОГДА Результат.ИмяКолонки = "Поле_Б" ТОГДА Результат.Значение ИНАЧЕ 0 КОНЕЦ) КАК Поле_Б
ИЗ
(ВЫБРАТЬ
втДанные.Объект,
втДанные.ИмяКолонки,
втДанные.Значение
ИЗ
втПоследниеДаты КАК Даты
ВНУТРЕННЕЕ СОЕДИНЕНИЕ втДанные КАК втДанные
ПО Даты.Объект = втДанные.Объект
И Даты.ИмяКолонки = втДанные.ИмяКолонки
И Даты.МаксПериод = втДанные.Период) КАК Результат
СГРУППИРОВАТЬ ПО
Результат.Объект
Если мы хотим избежать использования ОБЪЕДИНИТЬ ВСЕ, мы можем применить методику условного агрегирования. Выясним причину, почему это эффективно: мы сканируем основную таблицу регистра только один раз.
Рассмотрим пример формирования координат:
ВЫБРАТЬ
Объект,
МАКСИМУМ(ВЫБОР КОГДА Поле_А <> 0 ТОГДА Период ИНАЧЕ ДАТАВРЕМЯ(1,1,1) КОНЕЦ) КАК ПериодА,
МАКСИМУМ(ВЫБОР КОГДА Поле_Б <> 0 ТОГДА Период ИНАЧЕ ДАТАВРЕМЯ(1,1,1) КОНЕЦ) КАК ПериодБ
ПОМЕСТИТЬ втКоординаты
ИЗ
РегистрСведений.ИмяРегистра
СГРУППИРОВАТЬ ПО
Объект
После этого мы просто соединяем таблицу втКоординаты с физической таблицей регистра несколько раз, каждый раз по своему полю периода. Это значительно быстрее на больших объемах данных, особенно под управлением СУБД PostgreSQL.
Проанализируем ситуацию с точки зрения проектирования. Если такая задача возникает регулярно, это может быть признаком «кривой» архитектуры. Рассмотрим два способа исправления самой структуры данных:
ПередЗаписью мы считываем текущий СрезПоследних. Если в записываемой строке есть пустые ресурсы, мы принудительно заполняем их данными из предыдущего среза. Таким образом, в регистре всегда хранятся «полные» слепки состояния, и обычный запрос СрезПоследних работает мгновенно и корректно.Свойство (ПланВидовХарактеристик) и один ресурс Значение. В этом случае каждое изменение любого поля — это отдельная строка. Стандартный срез последних по объекту и свойству всегда будет возвращать актуальное значение именно этого свойства.Выбор метода зависит от ваших условий: