Как получить последнюю строку по группировке в запросе 1С 7.7

Программист 1С v7.7 IT и автоматизация бизнеса
← На главную

При разработке отчетов и обработок в платформе 1С:Предприятие 7.7 нередко возникает задача получить только самую актуальную (последнюю по дате) запись для каждого объекта — например, последний отпуск или кадровое перемещение по сотруднику. В более современных версиях платформы для этого используются срез последних или функции языка запросов, однако в 1С 7.7 язык запросов имеет свои особенности.

Разберем типичную проблему: при попытке прервать выборку вложенной группировки с помощью оператора Прервать система может зависнуть или выдать некорректные данные. Выясним причину такого поведения и рассмотрим рабочие способы решения задачи.

Почему зависает обход запроса при вызове «Прервать»

Проанализируем механику работы объекта Запрос в 1С 7.7. Внутренний итератор выборки отслеживает указатели на текущие записи всех уровней группировок. Когда во внутреннем цикле выполняется принудительный выход через Прервать, курсор дочерней группировки остается в незавершенном состоянии. При следующем вызове метода Группировка() родительского уровня платформа пытается перестроить указатели, что часто приводит к бесконечному зацикливанию или аварийному завершению работы 1С.

По этой причине прерывать внутренний цикл группировки категорически не рекомендуется. Внутренний цикл всегда должен дойти до своего естественного конца.

Способ 1. Обратный обход группировки с программным флагом (рекомендуемый)

Рассмотрим основной и наиболее производительный подход. Метод Группировка() в 1С 7.7 поддерживает второй параметр, задающий направление обхода:

  1. 1 — прямой порядок (по возрастанию, значение по умолчанию);
  2. -1 — обратный порядок (по убыванию).

Если во вложенной группировке обходить данные в обратном направлении (от максимальной даты к минимальной), то самая поздняя запись всегда окажется первой в выборке. Чтобы обработать только её и пропустить остальные записи без разрыва структуры курсора, используем переменную-флаг.

Посмотрим на пример кода с правильной организацией двойного цикла:


Пока Запрос.Группировка(1) = 1 Цикл
    // Флаг для фиксации первой (самой последней по дате) строки
    Первый = 1;
    
    // Обход второй группировки в обратном направлении (-1)
    Пока Запрос.Группировка(2, -1) = 1 Цикл
        Если Первый = 1 Тогда
            ТабЗнач.НоваяСтрока();
            ТабЗнач.Сотрудник = Запрос.Сотрудник;
            ТабЗнач.ДатаНачалаПериода = Запрос.ДатаНачалаПериода;
            ТабЗнач.ДнейОтпуска = Запрос.ДнейСумма;
            
            Таб.ВывестиСекцию("ДатаНачалаОтпуска");
            
            // Сбрасываем флаг: остальные строки этой же группировки будут пропущены
            Первый = 0;
        КонецЕсли;
    КонецЦикла;
КонецЦикла;

Разберем по шагам, как отрабатывает этот алгоритм:

  1. На внешнем уровне выбирается очередной сотрудник.
  2. Переменная Первый инициализируется значением 1.
  3. Внутренний цикл начинает обход дат в обратном порядке (-1), поэтому на первой итерации мы получаем самую свежую запись.
  4. Условие Если Первый = 1 Тогда выполняется: данные выводятся в таблицу или отчет, а флаг сбрасывается в 0.
  5. Все последующие итерации внутреннего цикла выполняются вхолостую, курсор корректно доходит до конца, и внешний цикл без сбоев переходит к следующему сотруднику.

Способ 2. Постобработка результата через Таблицу Значений

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

Рассмотрим реализацию этого варианта:


// 1. Выгружаем результат запроса в плоскую таблицу
ТабЗнач = СоздатьОбъект("ТаблицаЗначений");
Запрос.Выгрузить(ТабЗнач, 1, 0);

// 2. Сортируем: по сотруднику по возрастанию, по дате — по убыванию
ТабЗнач.Сортировать("Сотрудник+,ДатаНачалаПериода-");

// 3. Обходим строки и отбираем первую запись по каждому сотруднику
ТекущийСотрудник = "";
ТабЗнач.ВыбратьСтроки();
Пока ТабЗнач.ПолучитьСтроку() = 1 Цикл
    Если ТабЗнач.Сотрудник <> ТекущийСотрудник Тогда
        // Это самая свежая запись по данному сотруднику
        ТекущийСотрудник = ТабЗнач.Сотрудник;
        
        Таб.ВывестиСекцию("ДатаНачалаОтпуска");
    КонецЕсли;
КонецЦикла;

Этот способ исключает зависимость от особенностей курсоров запроса и упрощает отладку, так как всю промежуточную таблицу можно быстро проверить и просмотреть на любом этапе выполнения.

← На главную