Как правильно выбрать данные из табличной части справочника в 1С при помощи запроса?

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

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

Постановка задачи и анализ исходных данных

Рассмотрим практическую ситуацию. У нас есть справочник ПодалиЗаявление (абитуриенты) и справочник ДокументыПриПоступлении (перечень возможных документов). В справочнике ПодалиЗаявление стандартный реквизит Наименование хранит ФИО абитуриента, а также создана табличная часть Документы со следующими реквизитами:

  1. Документ (тип: СправочникСсылка.ДокументыПриПоступлении) — указывает, какой именно документ должен предоставить абитуриент.
  2. ОтметкаОПолучении (тип: Булево) — признак того, сдал ли абитуриент этот документ (Истина/Ложь).

Наша цель — написать запрос, который проверяет табличные части всех абитуриентов, находит несданные документы (где ОтметкаОПолучении равно Ложь) и выводит в окно сообщений ФИО абитуриента и конкретное название документа, который он не предоставил.

Разбор типичных ошибок начинающих разработчиков

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

Ошибка 1. Попытка выборки табличной части через основную таблицу справочника.

В первом сообщении темы автор пытается обратиться к табличной части через точку от основного справочника:


// Неправильный подход!
"ВЫБРАТЬ
|   ПодалиЗаявление.Ссылка КАК Ссылка,
|   ПодалиЗаявление.Наименование КАК ФИО,
|   ПодалиЗаявление.Документы.ОтметкаОПолучении КАК Предоставлено
|ИЗ
|   Справочник.ПодалиЗаявление КАК ПодалиЗаявление
|ГДЕ
|   ПодалиЗаявление.Документы.ОтметкаОПолучении = Ложь"

Почему этот запрос не работает или работает некорректно? В реляционных базах данных табличная часть физически хранится в отдельной таблице СУБД. Обращение к табличной части через точку от родительского объекта (ПодалиЗаявление.Документы...) в секции ВЫБРАТЬ или ГДЕ заставляет платформу создавать сложные вложенные выборки. Платформа 1С не поддерживает адекватную работу с коллекциями строк внутри одной строки родителя таким способом в рамках плоского SQL-запроса. Для корректной выборки строк необходимо делать запрос непосредственно к таблице табличной части.

Ошибка 2. Двойной вызов метода Следующий() в цикле обхода.

Рассмотрим фрагмент кода обхода выборки:


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

Метод Следующий() осуществляет переход к следующей записи в выборке результата запроса и возвращает Истина, если запись существует, или Ложь, если записи закончились. Вызывая метод Следующий() внутри условия цикла, а затем повторно внутри тела самого цикла (строка Перем1 = ВыборкаЗаписи.Следующий()), разработчик принудительно перепрыгивает через одну строку выборки. В результате половина данных просто теряется, а логика программы полностью ломается.

Ошибка 3. Избыточное левое соединение табличной части с родителем.

В финальном сообщении темы автор пришел к работающему, но крайне неоптимальному варианту запроса:


// Работающий, но неоптимальный запрос (антипаттерн)
"ВЫБРАТЬ
|   ПодалиЗаявление.Наименование КАК Наименование,
|   ПодалиЗаявлениеДокументы.НаименованиеДокумента КАК НаименованиеДокумента,
|   ПодалиЗаявлениеДокументы.ОтметкаОПолучении КАК ОтметкаОПолучении
|ИЗ
|   Справочник.ПодалиЗаявление.Документы КАК ПодалиЗаявлениеДокументы
|   ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ПодалиЗаявление КАК ПодалиЗаявление
|   ПО (ПодалиЗаявлениеДокументы.Ссылка = ПодалиЗаявление.Ссылка)
|ГДЕ
|   ПодалиЗаявлениеДокументы.ОтметкаОПолучении = ЛОЖЬ"

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

Когда мы пишем явное ЛЕВОЕ СОЕДИНЕНИЕ табличной части с таблицей родителя по полю Ссылка, мы заставляем СУБД выполнять лишнюю работу по сопоставлению таблиц. Платформа 1С позволяет обращаться к реквизитам родителя напрямую через точку от поля Ссылка табличной части. Например: ПодалиЗаявлениеДокументы.Ссылка.Наименование. При трансляции этого обращения в SQL-запрос СУБД построит максимально оптимальный план выполнения без лишних явных соединений.

Ошибка 4. Прямое сравнение булевого поля с литералами ЛОЖЬ/ИСТИНА.

Использование конструкции вида ОтметкаОПолучении = ЛОЖЬ работает, однако противоречит стандартам разработки фирмы "1С". Согласно правилам хорошего тона и требованиям оптимизации запросов, булевы поля следует проверять с помощью логического отрицания НЕ. То есть правильная запись выглядит так: ГДЕ НЕ ПодалиЗаявлениеДокументы.ОтметкаОПолучении.

Разрабатываем оптимальное решение шаг за шагом

Разобьем создание правильного алгоритма на логические этапы. Нам нужно получить плоскую таблицу, содержащую ФИО абитуриента и наименование документа, который он не сдал, а затем вывести эти данные пользователю.

Шаг 1. Обращение к правильной таблице в секции ИЗ

Источником данных для нашего запроса должна служить таблица табличной части справочника. Ее системное имя формируется как Справочник.[ИмяСправочника].[ИмяТабличнойЧасти]. В нашем случае это:

Справочник.ПодалиЗаявление.Документы КАК ПодалиЗаявлениеДокументы

Шаг 2. Получение данных родителя и связанных справочников

Чтобы получить ФИО абитуриента, мы обращаемся к реквизиту Наименование через ссылку-владельца строки табличной части:

ПодалиЗаявлениеДокументы.Ссылка.Наименование КАК ФИО

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

ПодалиЗаявлениеДокументы.Документ.Наименование КАК НаименованиеДокумента

Шаг 3. Оптимизация интерфейса с помощью группировки (ИТОГИ ПО)

Если мы просто выведем результат плоским списком, то для абитуриента, не сдавшего три документа, мы получим три повторяющиеся строки с его именем. Это неудобно для чтения. Гораздо красивее сгруппировать вывод. В 1С для этого используется предложение ИТОГИ ПО. Мы сгруппируем результаты по полю ФИО абитуриента. Это позволит нам построить иерархический вывод в виде дерева.

Написание эталонного кода

Объединим все наши выводы и оптимизации в один готовый программный блок. Посмотрим на пример реализации:


// Создаем объект Запрос
Запрос = Новый Запрос;

// Пишем оптимизированный текст запроса по стандартам 1С
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ПодалиЗаявлениеДокументы.Ссылка.Наименование КАК ФИО,
    |	ПодалиЗаявлениеДокументы.Документ.Наименование КАК НаименованиеДокумента
    |ИЗ
    |	Справочник.ПодалиЗаявление.Документы КАК ПодалиЗаявлениеДокументы
    |ГДЕ
    |	НЕ ПодалиЗаявлениеДокументы.ОтметкаОПолучении
    |ИТОГИ ПО
    |	ФИО";

// Выполняем запрос
РезультатЗапроса = Запрос.Выполнить();

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

// Обходим абитуриентов, у которых есть долги по документам
Пока ВыборкаФИО.Следующий() Цикл
    
    Сообщить("Абитуриент " + ВыборкаФИО.ФИО + " не предоставил следующие документы:");
    
    // Получаем подчиненную выборку (детальные записи конкретных документов для данного ФИО)
    ВыборкаДокументы = ВыборкаФИО.Выбрать();
    
    Пока ВыборкаДокументы.Следующий() Цикл
        // Выводим конкретные названия несданных документов со смещением для красоты
        Сообщить("   - " + ВыборкаДокументы.НаименованиеДокумента);
    КонецЦикла;
    
КонецЦикла;

Как это работает изнутри? Подробный разбор кода

Давайте детально проанализируем, как выполняется этот алгоритм:

  1. Секция ИЗ: Запрос обращается напрямую к физической таблице табличной части Справочник.ПодалиЗаявление.Документы. СУБД мгновенно находит нужные строки, так как они сгруппированы в базе данных по идентификатору родителя.
  2. Фильтрация ГДЕ: Условие НЕ ПодалиЗаявлениеДокументы.ОтметкаОПолучении отсекает все строки, где флаг получения равен Истина. В выборку попадают только «должники». Это минимизирует объем данных, передаваемых с сервера СУБД на сервер приложений 1С.
  3. Иерархический обход: Использование параметра ОбходРезультатаЗапроса.ПоГруппировкам в методе Выбрать() позволяет нам сначала получить список уникальных должников (первый цикл Пока ВыборкаФИО.Следующий()).
  4. Подчиненный обход: Вызов метода Выбрать() без параметров внутри первого цикла (ВыборкаФИО.Выбрать()) возвращает детальные записи (документы) только для текущего абитуриента, находящегося в фокусе первого цикла. Это избавляет нас от написания сложных алгоритмов ручной группировки во встроенном языке 1С.

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

← На главную