Как получить количество строк в документе внутри запроса для управления выводом печатной формы

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

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

Проблема обращений «через точку» в цикле

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

Проанализируем, почему это плохая практика. Каждый раз, когда код обращается к ДанныеДляПечати.Ссылка.Начисления, платформа 1С выполняет отдельный SQL-запрос к базе данных, чтобы получить содержимое табличной части. Если вы печатаете реестр из 100 документов, вы получите 100 лишних запросов к БД. Это значительно замедляет формирование печатной формы и создает избыточную нагрузку на сервер. Наша задача — получить все необходимые данные, включая количество строк, одним запросом.

Решение №1: Использование итогов в запросе

Самый простой и эффективный способ узнать количество детальных записей в группе — это использование конструкции ИТОГИ. Давайте посмотрим на пример построения такого запроса:


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

При таком подходе, когда мы получаем выборку по группировкам, объект ВыборкаИзРезультатаЗапроса на уровне ссылки уже будет содержать агрегатное значение — количество строк. Разберем по шагам, как правильно обойти такой результат:


РезультатЗапроса = Запрос.Выполнить();
ВыборкаДокумент = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);

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

Решение №2: Использование метода «СледующийПоЗначениюПоля»

Если вы не хотите использовать ИТОГИ (например, если структура запроса и так сложная), можно использовать метод СледующийПоЗначениюПоля(). Однако стоит помнить, что в «плоской» выборке метод Количество() возвращает общее число строк во всем результате запроса, а не в конкретной группе. Чтобы узнать количество строк именно в текущем документе, придется либо заранее подготовить данные, либо использовать пакетные запросы.

Рассмотрим вариант с пакетным запросом. Мы можем сначала поместить данные во временную таблицу, а затем соединить её саму с собой, чтобы посчитать количество строк по каждому документу.


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

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

Решение №3: Трюк со копированием выборки

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

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


Пока ВыборкаСтрок.Следующий() Цикл
    // Создаем копию выборки
    КопияВыборки = ВыборкаСтрок.Скопировать();
    
    // Проверяем, есть ли следующая запись
    ЭтоПоследняяСтрока = НЕ КопияВыборки.Следующий();
    
    // Используем результат для логики вывода
    // ...
КонецЦикла;

Этот метод работает быстро, так как Скопировать() не выполняет новый запрос к БД, а просто дублирует текущий указатель в памяти. Однако он применим только тогда, когда вам нужно просто знать факт «последней строки», а не конкретное число (например, для вывода «Итого» по документу это не подойдет).

Проверка вывода подвала: важные нюансы

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

Чтобы избежать этого, мы всегда должны проверять вывод последней строки вместе с подвалом. Рассмотрим правильный алгоритм работы с областями макета:

  1. Получаем области из макета: Шапка, Строка, Подвал.
  2. В цикле по строкам заполняем параметры текущей строки.
  3. Если строка последняя, создаем массив: МассивОбластей.Добавить(ОбластьСтрока); МассивОбластей.Добавить(ОбластьПодвал);.
  4. Если ПроверитьВывод(МассивОбластей) возвращает Ложь, принудительно выводим разделитель страниц и, если нужно, повторяем заголовок таблицы.
  5. Выводим область строки.
  6. После завершения цикла выводим область подвала (она гарантированно уместится или перенесется корректно вместе с последней строкой).

Выясним причину, почему метод ПроверитьВывод() иногда может ошибаться. Это происходит, если параметры в областях еще не заполнены. Высота строки может измениться, если текст внутри ячейки переносится на несколько строк. Поэтому всегда заполняйте параметры области (Область.Параметры.Заполнить()) до вызова ПроверитьВывод().

Заключение

Мы рассмотрели три способа получения количества строк в документе при обходе запроса. Оптимальным с точки зрения производительности и удобства является использование ИТОГОВ в сочетании с методом Выборка.Количество(). Это позволяет избежать «проблемы N+1 запроса» и обеспечивает код всей необходимой информацией для управления макетом. Применяя эти знания, мы создаем быстрые и качественные печатные формы, которые корректно ведут себя при любом объеме данных.

← На главную