При разработке отчетов по маркетинговым активностям часто возникает задача вывести периоды действия скидок в формате «Дата начала — Дата окончания». В типовых конфигурациях, таких как 1С:Управление торговлей 11 или 1С:ERP, информация о статусах скидок хранится в периодическом регистре сведений ДействиеСкидокНаценок. Однако попытка получить эти данные простым запросом к виртуальной таблице часто оборачивается логическими ошибками или техническими трудностями в конструкторе запросов.
В этой статье мы подробно разберем, почему не стоит использовать срез последних для истории, как справиться с «противоречивыми связями» при работе с составными типами и как правильно состыковать записи о включении и отключении скидок.
Типичная ошибка начинающего разработчика — попытка использовать виртуальную таблицу СрезПоследних для получения интервалов. Рассмотрим ситуацию: скидка была включена 1 января и выключена 15 января. Если мы обратимся к срезу последних на 31 января, система выдаст нам только одну строку: статус «Не действует».
Виртуальная таблица СрезПоследних предназначена для получения состояния на одну конкретную точку во времени. Она не хранит историю переключений. Для построения отчета, который показывает, когда скидка началась и когда закончилась, необходимо обращаться к основной (физической) таблице регистра сведений. Только там содержатся все записи об изменениях, которые мы сможем сопоставить друг с другом.
В регистре ДействиеСкидокНаценок измерение Источник часто имеет составной тип данных (например, Справочник.СоглашенияСКлиентами, Справочник.СегментыПартнеров и другие). При попытке объединить или соединить таблицы по такому полю в Конструкторе запросов 1С может возникнуть ошибка «Противоречивая связь».
Это происходит потому, что Конструктор пытается сопоставить метаданные полей, и если типы в разных временных таблицах определены неявно, он выдает предупреждение. Чтобы этого избежать, проанализируем использование оператора ВЫБОР. Вместо сложной конструкции с проверкой ссылки лучше использовать оператор ВЫРАЗИТЬ. Это не только успокоит Конструктор, но и оптимизирует работу СУБД, так как ей не придется сканировать все таблицы, входящие в составной тип.
Пример правильной типизации поля Источник:
ВЫБРАТЬ
ВЫРАЗИТЬ(ДействиеСкидокНаценок.Источник КАК Справочник.СоглашенияСКлиентами) КАК Соглашение,
ДействиеСкидокНаценок.СкидкаНаценка КАК СкидкаНаценка,
ДействиеСкидокНаценок.Период КАК Период,
ДействиеСкидокНаценок.Статус КАК Статус
ПОМЕСТИТЬ ВТ_ИсходныеДанные
ИЗ
РегистрСведений.ДействиеСкидокНаценок КАК ДействиеСкидокНаценок
Чтобы получить одну строку с датой начала и датой окончания, мы должны выполнить самосоединение таблицы регистра. Проанализируем логику: для каждой записи, где статус равен «Действует», нам нужно найти самую раннюю следующую по времени запись с тем же соглашением и скидкой, но со статусом «Не действует».
Разберем по шагам создание такого запроса:
СтатусыДействияСкидок.Действует.ЛЕВОЕ СОЕДИНЕНИЕ этой же таблицы по условию совпадения измерений (Скидка, Источник).МИНИМУМ от найденных дат окончания.Посмотрим на практический пример реализации этого алгоритма:
ВЫБРАТЬ
Действует.Источник КАК Источник,
Действует.СкидкаНаценка КАК СкидкаНаценка,
Действует.Период КАК ДатаНачала,
МИНИМУМ(НеДействует.Период) КАК ДатаОкончания
ИЗ
РегистрСведений.ДействиеСкидокНаценок КАК Действует
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ДействиеСкидокНаценок КАК НеДействует
ПО Действует.Источник = НеДействует.Источник
И Действует.СкидкаНаценка = НеДействует.СкидкаНаценка
И НеДействует.Статус = ЗНАЧЕНИЕ(Перечисление.СтатусыДействияСкидок.НеДействует)
И Действует.Период < НеДействует.Период
ГДЕ
Действует.Статус = ЗНАЧЕНИЕ(Перечисление.СтатусыДействияСкидок.Действует)
СГРУППИРОВАТЬ ПО
Действует.Источник,
Действует.СкидкаНаценка,
Действует.Период
В реальной базе данных многие скидки могут действовать «бессрочно» на текущий момент. В этом случае ЛЕВОЕ СОЕДИНЕНИЕ не найдет запись со статусом «Не действует», и поле ДатаОкончания будет содержать NULL. Чтобы отчет выглядел корректно, проанализируем ситуацию и подставим пустую дату или «бесконечность» (например, 31.12.3999) с помощью функции ЕСТЬNULL.
Если вы работаете на современной версии платформы 1С (8.3.21 и выше), задачу можно решить значительно эффективнее с помощью оконных функций. Это избавит базу данных от тяжелого соединения таблиц саму с собой.
Рассмотрим, как это работает: функция LEAD позволяет «заглянуть» в следующую строку выборки. Мы можем сгруппировать данные по скидке и источнику, отсортировать по периоду и сразу получить период следующей записи.
ВЫБРАТЬ
Источник,
СкидкаНаценка,
Статус,
Период КАК ДатаНачала,
LEAD(Период) OVER (PARTITION BY Источник, СкидкаНаценка ORDER BY Период) КАК ДатаОкончания
ИЗ
РегистрСведений.ДействиеСкидокНаценок
ГДЕ
Статус = ЗНАЧЕНИЕ(Перечисление.СтатусыДействияСкидок.Действует)
Важный нюанс: Оконные функции работают очень быстро, но они доступны только при определенных настройках режима совместимости конфигурации. Если ваша база находится на старой версии, используйте классический метод с МИНИМУМ и группировкой, описанный выше.
Подведем итоги нашего анализа. Для корректного построения периодов действия объектов в 1С придерживайтесь следующих правил:
СрезПоследних для отчетов, где важна история изменений.ВЫРАЗИТЬ, чтобы избежать ошибок Конструктора и ускорить выполнение запроса.МИНИМУМ даты окончания, чтобы не получить «размножение» строк в результате перекрытия периодов.Используя эти методы, вы сможете строить точные и производительные отчеты по любым периодическим регистрам сведений, будь то цены номенклатуры, статусы скидок или курсы валют.