Заполнение табличной части (ТЧ) документа или справочника результатами запроса — одна из самых распространенных задач, с которой сталкивается любой начинающий разработчик в среде 1С:Предприятие. На первый взгляд задача кажется простой, но при её реализации новички часто совершают классические ошибки: организуют запросы внутри циклов, путают типы данных в параметрах или пытаются вручную перебирать строки там, где платформа предлагает готовые оптимизированные механизмы. В этой статье мы подробно разберем, как правильно и быстро решать эту задачу, проанализируем типичные ошибки и рассмотрим три различных способа заполнения табличной части в зависимости от ваших целей.
Рассмотрим код, с которого начинал автор темы на форуме:
ТабЧасть = Объект.Абоненты.Выгрузить();
Для каждого Строка из ТабЧасть Цикл
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
| Абоненты.Ссылка КАК Ссылка
|ИЗ
| Справочник.Абоненты КАК Абоненты
|ГДЕ
| Абоненты.ТипТелефона = &ТипТелефона";
Запрос.УстановитьПараметр("ТипТелефона","Обычный");
Результат = Запрос.Выполнить();
Выборка = Результат.Выбрать();
КонецЦикла
В этом небольшом фрагменте кода сосредоточено сразу несколько критических архитектурных ошибок, которые не позволят программе работать корректно или сделают её работу крайне медленной. Выясним причины этих ошибок и разберем их подробно.
Ошибка первая: Запрос внутри цикла (Anti-pattern)
Запуск нового запроса к базе данных на каждой итерации цикла — это одна из самых грубых ошибок в программировании на платформе 1С. Почему так делать нельзя? Каждое выполнение метода Выполнить() генерирует полноценное обращение к СУБД (например, MS SQL Server или PostgreSQL). Если ваша табличная часть содержит 1000 строк, система совершит 1000 отдельных запросов по сети. Это приведет к колоссальной нагрузке на сервер, взаимным блокировкам таблиц и зависанию интерфейса пользователя. На реальных базах данных такой код просто парализует работу системы, а на экзамене «1С:Специалист» за подобное решение вы гарантированно получите неудовлетворительную оценку.
Ошибка вторая: Несоответствие типов данных в параметрах
Автор пытается установить строковый параметр: Запрос.УстановитьПараметр("ТипТелефона", "Обычный"). Однако реквизит ТипТелефона в справочнике Абоненты, скорее всего, имеет тип ссылки (например, СправочникСсылка.ТипыТелефонов), а не простой текстовой строки. Для СУБД ссылка и строка — это абсолютно разные сущности. Попытка сравнить их напрямую приведет к тому, что запрос всегда будет возвращать пустой результат. Платформа не сможет сопоставить уникальный идентификатор (GUID) ссылки с текстовым литералом.
Ошибка третья: Отсутствие механизмов записи данных в ТЧ
Автор выгрузил табличную часть во временную таблицу значений ТабЧасть = Объект.Абоненты.Выгрузить(), обошел её циклом, получил выборку из запроса, но так и не связал эти данные с исходным объектом. Изменения во временной таблице значений никак автоматически не переносятся обратно в реальную табличную часть объекта, если не вызвать соответствующий метод.
Теперь, когда мы проанализировали основные ошибки, перейдем к правильным методам реализации этой задачи. Разберем по шагам три основных способа добавления данных из запроса в табличную часть.
Этот способ идеально подходит, когда вам нужно дописать новые строки в табличную часть, сохранив при этом уже существующие в ней записи. Рассмотрим подробнее правильный алгоритм без использования запросов в цикле.
Сначала мы подготовим запрос, который выберет все необходимые данные за один раз. Затем мы обойдем результат запроса в цикле и добавим новые строки в табличную часть объекта с помощью метода Добавить().
// 1. Создаем запрос и описываем его текст
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Абоненты.Ссылка КАК ФИО
|ИЗ
| Справочник.Абоненты КАК Абоненты
|ГДЕ
| Абоненты.ТипТелефона = &ТипТелефона";
// 2. Правильно определяем параметр запроса (ищем ссылку по наименованию)
СсылкаТипТелефона = Справочники.ТипыТелефонов.НайтиПоНаименованию("Обычный");
Запрос.УстановитьПараметр("ТипТелефона", СсылкаТипТелефона);
// 3. Выполняем запрос и получаем выборку
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
// 4. Обходим выборку и добавляем строки в табличную часть
Пока Выборка.Следующий() Цикл
// Создаем новую строку в табличной части "Абоненты" нашего объекта
НоваяСтрока = Объект.Абоненты.Добавить();
// Заполняем реквизит "ФИО" в новой строке значением из выборки запроса
НоваяСтрока.ФИО = Выборка.ФИО;
КонецЦикла;
В этом примере мы исправили все ошибки: запрос выполняется ровно один раз на сервере, параметр передается в виде ссылки нужного типа (благодаря методу НайтиПоНаименованию()), а новые строки успешно создаются в контексте нашего объекта.
Если ваша задача состоит в том, чтобы полностью заменить содержимое табличной части результатами выполнения запроса (то есть старые строки нужно очистить, а новые записать), то использование циклов будет лишним. В платформе 1С для этого существует встроенный высокооптимизированный метод Загрузить().
Этот метод работает на уровне платформы крайне быстро. Чтобы воспользоваться им, необходимо соблюсти одно важное правило: имена колонок во временной таблице значений, получаемой из запроса, должны полностью совпадать с именами реквизитов вашей табличной части. В нашем случае реквизит табличной части называется ФИО, поэтому в запросе мы должны переименовать поле Ссылка с помощью конструкции КАК ФИО.
Рассмотрим, как выглядит этот лаконичный и быстрый код:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Абоненты.Ссылка КАК ФИО
|ИЗ
| Справочник.Абоненты КАК Абоненты
|ГДЕ
| Абоненты.ТипТелефона = &ТипТелефона";
// Устанавливаем параметр-ссылку
Запрос.УстановитьПараметр("ТипТелефона", Справочники.ТипыТелефонов.НайтиПоНаименованию("Обычный"));
// Выполняем запрос и сразу выгружаем его результат в Таблицу Значений
ТаблицаРезультатов = Запрос.Выполнить().Выгрузить();
// Загружаем полученную таблицу значений напрямую в табличную часть объекта.
// При этом старые строки ТЧ автоматически очищаются, а новые создаются.
Объект.Абоненты.Загрузить(ТаблицаРезультатов);
Этот способ является стандартом разработки в 1С при полной перезаписи табличных частей. Он минимизирует количество строк кода и работает значительно быстрее ручного перебора.
Что делать, если вам все-таки нужно добавлять строки в цикле (например, по пути требуется выполнять сложные дополнительные проверки или расчеты), но колонок в табличной части много, и писать для каждой строки конструкции вида НоваяСтрока.Реквизит1 = Выборка.Реквизит1 слишком долго? В таких случаях на помощь приходит универсальная функция 1С ЗаполнитьЗначенияСвойств().
Эта функция автоматически сопоставляет свойства источника и приемника по их именам и копирует значения только в совпадающие реквизиты. Посмотрим на практический пример ее использования:
Запрос = Новый Запрос;
// Выбираем несколько полей, имена которых совпадают с реквизитами ТЧ
Запрос.Текст =
"ВЫБРАТЬ
| Абоненты.Ссылка КАК ФИО,
| Абоненты.НомерТелефона КАК НомерТелефона,
| Абоненты.Адрес КАК Адрес
|ИЗ
| Справочник.Абоненты КАК Абоненты
|ГДЕ
| Абоненты.ТипТелефона = &ТипТелефона";
Запрос.УстановитьПараметр("ТипТелефона", Справочники.ТипыТелефонов.НайтиПоНаименованию("Обычный"));
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
// Создаем новую пустую строку ТЧ
НоваяСтрока = Объект.Абоненты.Добавить();
// Автоматически заполняем все совпадающие реквизиты (ФИО, НомерТелефона, Адрес)
ЗаполнитьЗначенияСвойств(НоваяСтрока, Выборка);
КонецЦикла;
Использование ЗаполнитьЗначенияСвойств() делает ваш код чистым, легко читаемым и масштабируемым: если в будущем в табличную часть добавятся новые реквизиты, вам достаточно будет лишь добавить их в запрос, а код внутри цикла менять не придется.
Как справедливо заметили участники форума, метод НайтиПоНаименованию() возвращает ссылку на элемент справочника. Поиск по наименованию в коде — рабочее решение, но у него есть минус: если пользователь изменит наименование «Обычный» в режиме предприятия (например, переименует его в «Обычный телефон»), ваш код перестанет работать, так как метод вернет пустую ссылку.
Чтобы избежать подобных проблем, в реальной практике рекомендуется использовать один из двух подходов:
Справочники.ТипыТелефонов.Обычный или использовать конструкцию ЗНАЧЕНИЕ(Справочник.ТипыТелефонов.Обычный) непосредственно внутри текста запроса, полностью отказавшись от передачи этого параметра через УстановитьПараметр().Подведем итог: всегда анализируйте, какую именно задачу вы решаете. Если нужно полностью обновить табличную часть — выбирайте Загрузить(). Если требуется дописать новые данные — используйте обход выборки с методом Добавить() и функцией ЗаполнитьЗначенияСвойств(), и никогда не выполняйте запросы внутри циклов!