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