Как сделать выборку из справочника с группировкой по реквизиту в 1С 7.7 и подсчитать связанные данные?

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

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

Метод 1. Использование встроенного объекта «Запрос»

Если нам необходимо получить данные справочника, сгруппированные по определенному реквизиту, самым быстрым и правильным решением будет использование встроенного языка запросов 1С 7.7. В отличие от версии 8.х, здесь используется свой синтаксис, однако визуальный конструктор запросов отлично помогает быстро набросать структуру.

Проанализируем пример построения такого запроса. Предположим, у нас есть справочник «Сотрудники», и нам нужно сгруппировать их по реквизиту «Должность»:


Перем Запрос, ТекстЗапроса, Таб;

// Создание объекта типа Запрос
Запрос = СоздатьОбъект("Запрос");

ТекстЗапроса = 
"//{{ЗАПРОС(Сформировать)
|Обрабатывать НеПомеченныеНаУдаление;
|Без итогов;
|Сотрудники = Справочник.Сотрудники.ТекущийЭлемент;
|Должность = Справочник.Сотрудники.Должность;
|Группировка Должность;
|Группировка Сотрудники без групп;
|"//}}ЗАПРОС
;

// Выполняем запрос и проверяем на ошибки
Если Запрос.Выполнить(ТекстЗапроса) = 0 Тогда
    Предупреждение("Ошибка при выполнении запроса!");
    Возврат;
EndЕсли;

// Инициализируем выходную таблицу (макет)
Таб = СоздатьОбъект("Таблица");
Таб.ИсходнаяTable("Сформировать");
Таб.ВывестиСекцию("Заголовок");

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

Таб.ТолькоПросмотр(1);
Таб.Показать("Отчет по сотрудникам", "");

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

Метод 2. Стандартный обход справочника с помощью «ПорядокРеквизита»

Если по каким-то причинам использование объекта Запрос нецелесообразно (например, требуется выполнять сложные интерактивные манипуляции с элементами «на лету»), мы можем применить объектную выборку. Для того чтобы выборка происходила в порядке определенного реквизита, в 1С 7.7 существует метод ПорядокРеквизита().

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

Посмотрим на пример кода выборки клиентов с сортировкой по реквизиту Дом:


Спр = СоздатьОбъект("Справочник.Клиенты");

// Устанавливаем сортировку по реквизиту "Дом"
// Важно: в Конфигураторе у реквизита "Дом" должен стоять флаг "Сортировка"!
Спр.ПорядокРеквизита("Дом");

Спр.ВыбратьЭлементы();

ДомДляСравнения = "";

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

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

Почему не работает «КоличествоСтрок()» и как получить данные экранных таблиц?

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

Колво = Спр.Ж.КоличествоСтрок(); // Ошибка!

Разберем, почему данный код принципиально неработоспособен в среде 1С 7.7:

  1. Отсутствие табличных частей у справочников: В архитектуре 1С 7.7 справочники не могут содержать встроенных табличных частей (это ограничение платформы, ТЧ есть только у документов). Любая «таблица» на форме элемента справочника — это элемент диалога типа ТаблицаЗначений.
  2. Отсутствие контекста формы: Когда мы пишем СоздатьОбъект("Справочник.Клиенты") и перебираем элементы в цикле, сама экранная форма элемента не открывается. Процедура ПриОткрытии(), которая программно заполняет таблицу Ж данными, не выполняется. Соответственно, объект Ж в памяти не существует или равен нулю.

Как же правильно получить эти связанные данные программно? В 1С 7.7 связь «один ко многим» реализуется через механизм подчиненных справочников. На форме владельца таблица значений Ж при открытии заполняется элементами этого подчиненного справочника. Чтобы получить количество этих строк в коде отчета, нам необходимо программно обратиться к подчиненному справочнику.

Рассмотрим пример правильного подсчета количества связанных записей:


СпрВладелец = СоздатьОбъект("Справочник.Клиенты");
СпрПодчиненный = СоздатьОбъект("Справочник.Договоры"); // Подчинен справочнику Клиенты

СпрВладелец.ВыбратьЭлементы();
Пока СпрВладелец.ПолучитьЭлемент() = 1 Цикл
    Если СпрВладелец.ЭтоГруппа() = 1 Тогда
        Продолжить;
    КонецЕсли;
    
    // Устанавливаем фильтр по владельцу. 
    // Метод ТекущийЭлемент() обязателен для позиционирования!
    СпрПодчиненный.ИспользоватьВладельца(СпрВладелец.ТекущийЭлемент());
    СпрПодчиненный.ВыбратьЭлементы();
    
    КолвоПодчиненных = 0;
    Пока СпрПодчиненный.ПолучитьЭлемент() = 1 Цикл
        КолвоПодчиненных = КолвоПодчиненных + 1;
    КонецЦикла;
    
    Сообщить("Клиент: " + СпрВладелец.Наименование + " | Договоров: " + КолвоПодчиненных);
КонецЦикла;

Профессиональный лайфхак: Эмуляция формы элемента на форме списка

В процессе обсуждения разработчики упомянули интересный прием проектирования интерфейсов, который позволяет обойти неудобства разработки связки «Владелец -> Таблица значений». Классический подход требует написания огромного объема кода для синхронизации виртуальной таблицы на форме владельца с физическими записями подчиненного справочника.

Суть эмуляции:

Вместо формы элемента справочника-владельца за основу берется форма списка подчиненного справочника:

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

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

← На главную