Как выбрать последние 5 документов по каждому сотруднику в запросе 1С

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В практике разработчика 1С часто возникает задача: получить не просто «последние документы», а ограниченное количество записей для каждого объекта из списка. Например, нам нужно увидеть 5 последних начислений зарплаты по каждому сотруднику в одном отчете. Обычный оператор ВЫБРАТЬ ПЕРВЫЕ здесь не поможет, так как он ограничит выборку всего запроса, а не внутри конкретной группы. Рассмотрим подробно, как решить эту задачу несколькими способами, проанализируем их эффективность и выберем оптимальный вариант.

Способ 1. Использование самосоединения таблиц (универсальный метод)

Этот метод часто называют «треугольным соединением». Его суть заключается в том, что мы соединяем таблицу саму с собой по условию «больше или равно». Таким образом, для каждой записи мы считаем, сколько записей находится «после» нее по временной шкале. Рассмотрим этот процесс по шагам.

Сначала подготовим временную таблицу со всеми необходимыми данными. Важный нюанс: использовать только поле Дата для сравнения небезопасно, так как документы могут быть созданы в одну секунду. Правильнее использовать МоментВремени или комбинацию даты и ссылки.

Разберем пример запроса:


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

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

////////////////////////////////////////////////////////////////////////////////
// Выбираем только те записи, где номер не превышает 5
ВЫБРАТЬ
    Результат.Сотрудник,
    Результат.Документ,
    Результат.ПорядковыйНомер
ИЗ
    ВТ_ПронумерованныеДокументы КАК Результат
ГДЕ
    Результат.ПорядковыйНомер <= 5
УПОРЯДОЧИТЬ ПО
    Результат.Сотрудник,
    Результат.ПорядковыйНомер

Проанализируем ситуацию: этот запрос работает по принципу «для каждого документа считаем, сколько документов того же сотрудника новее его». Если таких документов меньше или равно 5, значит, текущий документ входит в нужную нам выборку. Обратите внимание на условие соединения — оно гарантирует уникальность номера даже при совпадении дат.

Способ 2. Использование оконных функций (Платформа 8.3.20+)

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

Выясним причину, почему это лучше: СУБД выполняет расчет порядкового номера за один проход по индексу, не создавая гигантских декартовых произведений при соединении таблиц. Посмотрим на пример реализации:


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

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ.Сотрудник,
    ВТ.Документ
ИЗ
    ВТ_СНомерами КАК ВТ
ГДЕ
    ВТ.НомерВГруппе <= 5

Здесь ключевым объектом является функция ПОРЯДКОВЫЙ_НОМЕР(). Конструкция РАСПРЕДЕЛИТЬ ПО определяет поле группировки (сотрудник), а УПОРЯДОЧИТЬ ПО — критерий, по которому мы определяем «последние» документы. Это самый современный и производительный подход.

Способ 3. Система компоновки данных (СКД)

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

Разберем по шагам, как это настроить:

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

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

Способ 4. Динамическое построение текста запроса (UNION ALL)

Рассмотрим ситуацию, когда количество сотрудников в списке невелико (например, 2-3 человека, как в исходном сообщении темы), а платформа старая. В этом случае самым быстрым решением будет программное формирование запроса с объединением результатов.

Проанализируем пример кода на языке 1С:


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

Хотя этот метод выглядит как «костыль», для СУБД это самый легкий вариант, так как оператор ВЫБРАТЬ ПЕРВЫЕ мгновенно прекращает поиск после нахождения 5 записей по конкретному индексу. Однако помните, что при большом количестве сотрудников (более 50-100) такой запрос может стать слишком громоздким и вызвать ошибку переполнения стека парсера запросов.

Важные рекомендации по производительности

Независимо от выбранного метода, следует придерживаться следующих правил:

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

← На главную