Как получить один последний документ по каждой дисконтной карте в запросе или СКД?

Программист 1С v8.3 (Управляемые формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

В процессе разработки отчетов в 1С часто возникает задача получить срез последних данных по аналитике, для которой не предусмотрен периодический регистр сведений. Типичный пример: по каждой дисконтной карте требуется вывести ровно один последний документ продажи или движение регистра. Попытка использовать ключевое слово ПЕРВЫЕ 1 в обычном запросе с группировкой приводит к ошибке: платформа возвращает ровно одну строку для всей выборки, а не по одной записи на каждую карту.

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

Анализ частых ошибок в запросах

Проанализируем ошибки, с которыми чаще всего сталкиваются разработчики при попытке решить эту задачу:

  1. Использование ПЕРВЫЕ 1 в едином запросе: конструкция языка запросов 1С ПЕРВЫЕ N ограничивает общее количество строк в итоговом результате выборки, а не внутри каждой отдельной группы.
  2. Соединение только по дате: если во временной таблице получить МАКСИМУМ(Дата) и соединить её с таблицей документов только по полю Дата, в результат попадут чужие документы других карт, оформленные в ту же секунду. Соединять необходимо обязательно и по дате, и по аналитике (карте).
  3. Совпадение дат и дублирование строк: если по одной карте зафиксировано несколько продаж в одну секунду, соединение по дате и карте вернет все эти документы. Чтобы гарантировать выборку строго одного документа, требуется дополнительная агрегация по ссылке.
  4. Зависание на больших базах из-за отсутствия индексов: соединение больших таблиц с временными таблицами без инструкции ИНДЕКСИРОВАТЬ ПО приводит к полному сканированию таблиц и резкому падению производительности.
  5. Использование виртуальной таблицы Обороты с периодичностью Регистратор: такая таблица не использует итоги регистров накопления и генерирует тяжелый подзапрос. Чтение напрямую из физической таблицы движений работает значительно быстрее.

Способ 1. Оптимизированный пакетный запрос с временной таблицей

Рассмотрим классический и наиболее надежный способ решения задачи через пакетный запрос. Сначала определим максимальную дату по каждой карте, проиндексируем промежуточный результат, а затем получим конкретный документ. Для исключения дублирования при одинаковом времени применим функцию МАКСИМУМ к ссылочному полю Ссылка или Регистратор.

Посмотрим на пример запроса к документу продажи:


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

ИНДЕКСИРОВАТЬ ПО
    ДисконтнаяКарта,
    Дата
;

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

Разберем по шагам, почему эта конструкция эффективна: предложение ИНДЕКСИРОВАТЬ ПО обеспечивает мгновенное соединение в СУБД, а повторная группировка с МАКСИМУМ(ДокументПродажи.Ссылка) гарантирует возврат ровно одной записи, даже если по карте есть два документа с абсолютно одинаковым временем.

Способ 2. Получение последнего документа из регистра накопления

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

Посмотрим на пример реализации:


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

ИНДЕКСИРОВАТЬ ПО
    ДисконтнаяКарта,
    Период
;

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

Способ 3. Реализация в СКД через связь наборов данных и «ПЕРВЫЕ 1»

Конструкция ПЕРВЫЕ 1 может быть использована по назначению, если настроить параметрическую связь двух наборов данных в схеме компоновки данных. В этом случае СКД самостоятельно выполняет подчиненный запрос для каждой переданной карты.

Разберем порядок настройки:

  1. Набор данных 1 («НаборДанныхКарты»): запрос выбирает уникальный перечень дисконтных карт из справочника или таблицы продаж.
  2. Набор данных 2 («НаборДанныхДокумент»): содержит запрос с ограничением первых записей и параметром:
    
    ВЫБРАТЬ ПЕРВЫЕ 1
        Движения.Регистратор КАК Регистратор,
        Движения.Период КАК Период
    ИЗ
        РегистрНакопления.ПродажиПоДисконтнымКартам КАК Движения
    ГДЕ
        Движения.ДисконтнаяКарта = &ДисконтнаяКарта
    УПОРЯДОЧИТЬ ПО
        Период УБЫВ,
        Регистратор УБЫВ
    
  3. Настройка связей наборов данных: на вкладке «Связи наборов данных» указываем источник — НаборДанныхКарты, приемник — НаборДанныхДокумент. В выражении источника выбираем ДисконтнаяКарта, а в параметре приемника указываем ДисконтнаяКарта.

Важный нюанс: данный способ удобен при наличии жестких отборов по небольшому количеству карт. Если отчет формируется по всей базе с сотнями тысяч карт, выполнение параметрических запросов пакетами может уступать по скорости единому пакетному запросу с индексами из Способа 1.

Способ 4. Фильтрация в СКД с помощью поля «НомерВГруппе»

Еще один вариант компоновщика данных заключается в получении всех записей с последующей фильтрацией средствами группировок отчета:

  1. В запросе выбираем карты и документы с упорядочиванием по дате убывающе.
  2. В настройках СКД создаем группировку по полю ДисконтнаяКарта.
  3. Внутри группировки карты создаем вложенную группировку детальных записей.
  4. На вкладке «Отбор» для детальных записей добавляем системное условие: СистемныеПоля.НомерВГруппе Равно 1.

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

← На главную