При разработке отчетов и обработок в платформе 1С:Предприятие 7.7 нередко возникает задача получить только самую актуальную (последнюю по дате) запись для каждого объекта — например, последний отпуск или кадровое перемещение по сотруднику. В более современных версиях платформы для этого используются срез последних или функции языка запросов, однако в 1С 7.7 язык запросов имеет свои особенности.
Разберем типичную проблему: при попытке прервать выборку вложенной группировки с помощью оператора Прервать система может зависнуть или выдать некорректные данные. Выясним причину такого поведения и рассмотрим рабочие способы решения задачи.
Проанализируем механику работы объекта Запрос в 1С 7.7. Внутренний итератор выборки отслеживает указатели на текущие записи всех уровней группировок. Когда во внутреннем цикле выполняется принудительный выход через Прервать, курсор дочерней группировки остается в незавершенном состоянии. При следующем вызове метода Группировка() родительского уровня платформа пытается перестроить указатели, что часто приводит к бесконечному зацикливанию или аварийному завершению работы 1С.
По этой причине прерывать внутренний цикл группировки категорически не рекомендуется. Внутренний цикл всегда должен дойти до своего естественного конца.
Рассмотрим основной и наиболее производительный подход. Метод Группировка() в 1С 7.7 поддерживает второй параметр, задающий направление обхода:
1 — прямой порядок (по возрастанию, значение по умолчанию);-1 — обратный порядок (по убыванию).Если во вложенной группировке обходить данные в обратном направлении (от максимальной даты к минимальной), то самая поздняя запись всегда окажется первой в выборке. Чтобы обработать только её и пропустить остальные записи без разрыва структуры курсора, используем переменную-флаг.
Посмотрим на пример кода с правильной организацией двойного цикла:
Пока Запрос.Группировка(1) = 1 Цикл
// Флаг для фиксации первой (самой последней по дате) строки
Первый = 1;
// Обход второй группировки в обратном направлении (-1)
Пока Запрос.Группировка(2, -1) = 1 Цикл
Если Первый = 1 Тогда
ТабЗнач.НоваяСтрока();
ТабЗнач.Сотрудник = Запрос.Сотрудник;
ТабЗнач.ДатаНачалаПериода = Запрос.ДатаНачалаПериода;
ТабЗнач.ДнейОтпуска = Запрос.ДнейСумма;
Таб.ВывестиСекцию("ДатаНачалаОтпуска");
// Сбрасываем флаг: остальные строки этой же группировки будут пропущены
Первый = 0;
КонецЕсли;
КонецЦикла;
КонецЦикла;
Разберем по шагам, как отрабатывает этот алгоритм:
Первый инициализируется значением 1.-1), поэтому на первой итерации мы получаем самую свежую запись.Если Первый = 1 Тогда выполняется: данные выводятся в таблицу или отчет, а флаг сбрасывается в 0.Если логика отчета сложная и в запросе требуется собирать много разнородных данных, удобнее выгрузить весь результат в ТаблицаЗначений и выполнить сортировку и фильтрацию уже средствами встроенного языка.
Рассмотрим реализацию этого варианта:
// 1. Выгружаем результат запроса в плоскую таблицу
ТабЗнач = СоздатьОбъект("ТаблицаЗначений");
Запрос.Выгрузить(ТабЗнач, 1, 0);
// 2. Сортируем: по сотруднику по возрастанию, по дате — по убыванию
ТабЗнач.Сортировать("Сотрудник+,ДатаНачалаПериода-");
// 3. Обходим строки и отбираем первую запись по каждому сотруднику
ТекущийСотрудник = "";
ТабЗнач.ВыбратьСтроки();
Пока ТабЗнач.ПолучитьСтроку() = 1 Цикл
Если ТабЗнач.Сотрудник <> ТекущийСотрудник Тогда
// Это самая свежая запись по данному сотруднику
ТекущийСотрудник = ТабЗнач.Сотрудник;
Таб.ВывестиСекцию("ДатаНачалаОтпуска");
КонецЕсли;
КонецЦикла;
Этот способ исключает зависимость от особенностей курсоров запроса и упрощает отладку, так как всю промежуточную таблицу можно быстро проверить и просмотреть на любом этапе выполнения.