Как выбрать по N первых документов за каждый год в одном запросе 1С

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

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

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

Способ 1. Динамическое формирование текста запроса

Если периодов (лет) не слишком много, а производительность критична, самым быстрым методом будет генерация текста запроса программным способом. Мы разберем ситуацию, когда нам нужно получить документы за период с 2018 по 2024 год.

Рассмотрим алгоритм реализации:

  1. Определяем массив нужных годов или дат.
  2. В цикле формируем блоки ВЫБРАТЬ ПЕРВЫЕ 30....
  3. Объединяем эти блоки через оператор ОБЪЕДИНИТЬ ВСЕ.

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


ТекстЗапроса = "";
Для Каждого Год Из МассивГодов Цикл
    Если ТекстЗапроса <> "" Тогда
        ТекстЗапроса = ТекстЗапроса + " ОБЪЕДИНИТЬ ВСЕ ";
    КонецЕсли;
    
    ТекстЗапроса = ТекстЗапроса + 
    "ВЫБРАТЬ ПЕРВЫЕ 30
    |   Партии.ДокументОприходования КАК Документ,
    |   ГОД(Партии.Период) КАК ГодПериода
    |ИЗ
    |   РегистрНакопления.ПартииТоваровНаСкладах КАК Партии
    |ГДЕ
    |   Партии.Период МЕЖДУ &Начало" + Год + " И &Конец" + Год + "
    |   И ТИПЗНАЧЕНИЯ(Партии.ДокументОприходования) = ТИП(Документ.ПоступлениеТоваровУслуг)";
    
    Запрос.УстановитьПараметр("Начало" + Год, НачалоГода(Дата(Год, 1, 1)));
    Запрос.УстановитьПараметр("Конец" + Год, КонецГода(Дата(Год, 1, 1)));
КонецЦикла;

Преимущества: SQL-сервер идеально отрабатывает конструкцию TOP для каждого года, используя индексы по периоду.

Недостатки: Текст запроса становится громоздким, если количество групп (например, по номенклатуре) велико.

Способ 2. Нумерация строк во временных таблицах

Если мы не знаем список годов заранее или групп слишком много, воспользуемся методом имитации нумерации строк. Проанализируем этот подход по шагам.

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


ВЫБРАТЬ
    Партии.ДокументОприходования КАК Документ,
    НАЧАЛОПЕРИОДА(Партии.Период, ГОД) КАК Год,
    АВТОНОМЕРЗАПИСИ() КАК ОбщийНомер
ПОМЕСТИТЬ ВТ_ВсеДанные
ИЗ
    РегистрНакопления.ПартииТоваровНаСкладах КАК Партии
ГДЕ
    Партии.Организация = &Организация
УПОРЯДОЧИТЬ ПО
    Год, 
    Документ
;

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

////////////////////////////////////////////////////////////////////////////////
// Вычисляем относительный номер и фильтруем ТОП-30
ВЫБРАТЬ
    Данные.Документ,
    Данные.Год
ИЗ
    ВТ_ВсеДанные КАК Данные
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Минимумы КАК Минимумы
    ПО Данные.Год = Минимумы.Год
ГДЕ
    (Данные.ОбщийНомер - Минимумы.МинНомерВГоду + 1) <= 30

Важный нюанс: Чтобы нумерация внутри года была корректной, записи во временную таблицу должны попадать строго в нужном порядке. Используйте конструкцию УПОРЯДОЧИТЬ ПО при помещении во временную таблицу.

Способ 3. Использование коррелированного подзапроса (Самосоединение)

Рассмотрим ситуацию, когда АВТОНОМЕРЗАПИСИ() недоступен или не подходит. Мы можем использовать классическое соединение таблицы с самой собой. Идея проста: для каждой записи мы считаем, сколько записей "меньше" неё существует в рамках того же года.


ВЫБРАТЬ
    Т1.ДокументОприходования КАК Документ,
    НАЧАЛОПЕРИОДА(Т1.Период, ГОД) КАК Год
ИЗ
    РегистрНакопления.ПартииТоваровНаСкладах КАК Т1
ГДЕ
    (ВЫБРАТЬ
        КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Т2.ДокументОприходования)
    ИЗ
        РегистрНакопления.ПартииТоваровНаСкладах КАК Т2
    ГДЕ
        НАЧАЛОПЕРИОДА(Т2.Период, ГОД) = НАЧАЛОПЕРИОДА(Т1.Период, ГОД)
        И Т2.ДокументОприходования < Т1.ДокументОприходования) < 30

Внимание: Этот метод крайне ресурсозатратный. На больших таблицах (как ПартииТоваровНаСкладах в УПП) он может привести к зависанию базы. Для оптимизации обязательно предварительно выгружайте данные во временную таблицу с индексами по полям Год и ДокументОприходования.

Оптимизация работы с составными типами

В регистре партий поле ДокументОприходования часто имеет составной тип. Выясним, почему это замедляет запрос: SQL-серверу приходится проверять связи со всеми возможными таблицами документов. Чтобы ускорить работу, всегда используйте ВЫРАЗИТЬ.

Сравним два варианта условия в соединении:

  1. Плохо: ПО Т1.ДокументОприходования = Т2.ДокументОприходования
  2. Хорошо: ПО ВЫРАЗИТЬ(Т1.ДокументОприходования КАК Документ.ПоступлениеТоваровУслуг) = ВЫРАЗИТЬ(Т2.ДокументОприходования КАК Документ.ПоступлениеТоваровУслуг)

Использование СКД для решения задачи

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

  1. В схеме компоновки создайте обычный запрос, выбирающий все документы.
  2. На вкладке "Настройки" добавьте группировку по полю ПериодГод.
  3. Внутри этой группировки добавьте детальные записи.
  4. Выберите группировку "Детальные записи" и перейдите на вкладку "Другие настройки".
  5. Найдите параметр "Количество записей" и установите значение 30.

В этом случае СКД сама сформирует эффективный алгоритм выборки, и вы получите по 30 документов на каждый год в итоговом отчете без написания сложного кода.

Итоги

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

← На главную