При разработке отчетов и обработок в 1С часто возникает задача получить самое актуальное состояние объекта — например, последний назначенный сегмент для номенклатуры. Если регистр сведений является периодическим, задача решается обращением к штатной виртуальной таблице СрезПоследних. Однако на практике разработчики нередко сталкиваются с нетиповыми структурами метаданных, когда регистр спроектирован непериодическим, а дата хранится в отдельном измерении или реквизите, либо связь между номенклатурой и историей изменения параметров распределена по нескольким таблицам.
Разберем типичную ситуацию, выясним причину дублирования строк при простой группировке и рассмотрим по шагам корректный алгоритм построения среза последних через временные таблицы.
Рассмотрим исходную постановку задачи. Имеется регистр сведений со связкой сегментов и номенклатуры, а также регистр, хранящий историю изменения коэффициентов или дат по сегментам. Попытка просто сгруппировать данные по номенклатуре и сегменту с расчетом максимального периода приводит к тому, что запрос возвращает все строки:
ВЫБРАТЬ
НоменклатураСегмента.Номенклатура КАК Номенклатура,
МАКСИМУМ(ТМАКСП.Период) КАК Период,
ТМАКСП.Сегмент КАК Сегмент
ИЗ
РегистрСведений.НоменклатураСегмента КАК НоменклатураСегмента
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ТМАКСП КАК ТМАКСП
ПО НоменклатураСегмента.Сегмент = ТМАКСП.Сегмент
СГРУППИРОВАТЬ ПО
ТМАКСП.Сегмент,
НоменклатураСегмента.Номенклатура
Проанализируем ситуацию: если номенклатура в разное время входила в несколько сегментов (например, весной — в один, летом — в другой), то при группировке по двум полям (НоменклатураСегмента.Номенклатура и ТМАКСП.Сегмент) СУБД вернет каждую уникальную комбинацию этих двух полей. Агрегатная функция МАКСИМУМ(ТМАКСП.Период) при такой группировке вернет максимальную дату именно для связки конкретного сегмента и конкретной позиции номенклатуры, а не абсолютный максимум по самой номенклатуре.
Чтобы получить строго одну запись по номенклатуре с ее самым последним сегментом, разберем по шагам алгоритм эмуляции среза последних:
Посмотрим на пример рабочего запроса, реализующего данный подход:
ВЫБРАТЬ
МАКСИМУМ(ПИК_КоэффСегмДляЗП.Период) КАК Период,
ПИК_КоэффСегмДляЗП.Сегмент КАК Сегмент
ПОМЕСТИТЬ ТМАКСП
ИЗ
РегистрСведений.ПИК_КоэффСегмДляЗП КАК ПИК_КоэффСегмДляЗП
СГРУППИРОВАТЬ ПО
ПИК_КоэффСегмДляЗП.Сегмент
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
НоменклатураСегмента.Номенклатура КАК Номенклатура,
МАКСИМУМ(ТМАКСП.Период) КАК Период
ПОМЕСТИТЬ НоменклатураМаксимумПериодСегмента
ИЗ
РегистрСведений.НоменклатураСегмента КАК НоменклатураСегмента
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ТМАКСП КАК ТМАКСП
ПО НоменклатураСегмента.Сегмент = ТМАКСП.Сегмент
СГРУППИРОВАТЬ ПО
НоменклатураСегмента.Номенклатура
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
НоменклатураСегмента.Номенклатура КАК Номенклатура,
ТМАКСП.Период КАК Период,
ТМАКСП.Сегмент КАК Сегмент
ИЗ
РегистрСведений.НоменклатураСегмента КАК НоменклатураСегмента
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ТМАКСП КАК ТМАКСП
ПО НоменклатураСегмента.Сегмент = ТМАКСП.Сегмент
ВНУТРЕННЕЕ СОЕДИНЕНИЕ НоменклатураМаксимумПериодСегмента КАК НоменклатураМаксимумПериодСегмента
ПО НоменклатураСегмента.Номенклатура = НоменклатураМаксимумПериодСегмента.Номенклатура
И ТМАКСП.Период = НоменклатураМаксимумПериодСегмента.Период
Рассмотрим важную деталь: что произойдет, если номенклатура входит в два разных сегмента, и у обоих сегментов совпадает дата максимального периода? В этом случае внутреннее соединение по совпадению периода вернет две строки на одну номенклатуру.
Чтобы гарантировать уникальность номенклатуры в результирующей выборке, добавим дополнительную группировку в финальный запрос и свернем сегмент через агрегатную функцию МАКСИМУМ. Для ссылочных типов платформы 1С сравнение происходит по внутреннему идентификатору ссылки (GUID), что исключает дублирование строк:
ВЫБРАТЬ
НоменклатураСегмента.Номенклатура КАК Номенклатура,
НоменклатураМаксимумПериодСегмента.Период КАК Период,
МАКСИМУМ(ТМАКСП.Сегмент) КАК Сегмент
ИЗ
РегистрСведений.НоменклатураСегмента КАК НоменклатураСегмента
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ТМАКСП КАК ТМАКСП
ПО НоменклатураСегмента.Сегмент = ТМАКСП.Сегмент
ВНУТРЕННЕЕ СОЕДИНЕНИЕ НоменклатураМаксимумПериодСегмента КАК НоменклатураМаксимумПериодСегмента
ПО НоменклатураСегмента.Номенклатура = НоменклатураМаксимумПериодСегмента.Номенклатура
И ТМАКСП.Период = НоменклатураМаксимумПериодСегмента.Период
СГРУППИРОВАТЬ ПО
НоменклатураСегмента.Номенклатура,
НоменклатураМаксимумПериодСегмента.Период
Обратите внимание на производительность: при работе с большими объемами данных во временных таблицах СУБД выполняет полное сканирование (Table Scan). Чтобы исключить замедление работы, обязательно индексируйте временные таблицы с помощью инструкции ИНДЕКСИРОВАТЬ ПО:
ПОМЕСТИТЬ ТМАКСП
ИЗ
РегистрСведений.ПИК_КоэффСегмДляЗП КАК ПИК_КоэффСегмДляЗП
СГРУППИРОВАТЬ ПО
ПИК_КоэффСегмДляЗП.Сегмент
ИНДЕКСИРОВАТЬ ПО
Сегмент,
Период
;
Индексация по полям связи позволит СУБД использовать эффективные алгоритмы соединения (Index Seek / Nested Loops / Hash Match), что существенно ускорит выполнение запроса в отчетах.