При работе с платформой 1С:Предприятие начинающие разработчики часто сталкиваются с ситуацией, когда запрос, идеально работающий в консоли запросов, выдает пустые значения или ведет себя непредсказуемо при исполнении в программном коде. Особенно часто это происходит при попытке извлечь данные из табличных частей документов. В этой статье мы подробно разберем, почему так происходит, изучим механизмы работы вложенных выборок и выясним, какой подход к написанию запросов считается наиболее эффективным и производительным.
Рассмотрим типичный сценарий. Разработчик пишет запрос к основной таблице документа, обращаясь к его табличной части через точку. Проанализируем пример такого кода:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ПеремещениеТоваров.Дата КАК Дата,
| ПеремещениеТоваров.Товары.Номенклатура КАК Номенклатура
|ИЗ
| Документ.ПеремещениеТоваров КАК ПеремещениеТоваров
|ГДЕ
| ПеремещениеТоваров.Ссылка В(&МассивОбъектов)";
Запрос.УстановитьПараметр("МассивОбъектов", МассивОбъектов);
РезультатЗапроса = Запрос.Выполнить();
ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
ДатаДокумента = ВыборкаДетальныеЗаписи.Дата;
Номенклатура = ВыборкаДетальныеЗаписи.Номенклатура; // Здесь получаем пустоту
КонецЦикла;
Выясним причину такого поведения. Когда в тексте запроса мы указываем путь ПеремещениеТоваров.Товары.Номенклатура, система 1С воспринимает поле Товары как вложенную таблицу. В консоли запросов предусмотрены специальные алгоритмы визуализации, которые автоматически "разворачивают" такие таблицы для удобства пользователя. Однако в программном коде объект ВыборкаИзРезультатаЗапроса работает иначе.
В данном случае поле Товары в каждой итерации цикла само является выборкой. Чтобы получить из него данные, нам необходимо запустить вложенный цикл. Рассмотрим, как должен выглядеть корректный обход такой выборки:
ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
ДатаДокумента = ВыборкаДетальныеЗаписи.Дата;
// Получаем вложенную выборку по табличной части "Товары"
ВыборкаТовары = ВыборкаДетальныеЗаписи.Товары.Выбрать();
Пока ВыборкаТовары.Следующий() Цикл
// Вот здесь мы получаем реальное значение номенклатуры из строки
Номенклатура = ВыборкаТовары.Номенклатура;
КонецЦикла;
КонецЦикла;
Важный момент: использование вложенных выборок удобно для построения иерархических отчетов, но оно может быть избыточным для простых задач обработки данных. Давайте проанализируем альтернативные и более оптимальные способы.
Опытные разработчики рекомендуют обращаться не к таблице документа, а напрямую к таблице его табличной части. В платформе 1С каждая табличная часть — это отдельная физическая таблица в базе данных SQL. Она содержит не только свои реквизиты, но и обязательное поле Ссылка, которое указывает на родительский документ.
Разберем пример запроса, который сразу возвращает "плоский" результат без вложенных таблиц:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Товары.Ссылка.Дата КАК Дата,
| Товары.Номенклатура КАК Номенклатура
|ИЗ
| Документ.ПеремещениеТоваров.Товары КАК Товары
|ГДЕ
| Товары.Ссылка В(&МассивОбъектов)";
Запрос.УстановитьПараметр("МассивОбъектов", МассивОбъектов);
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
// Данные доступны напрямую в одной выборке
Сообщить("Дата: " + Выборка.Дата + ", Товар: " + Выборка.Номенклатура);
КонецЦикла;
Преимущества этого метода:
Пока ... Цикл для обработки всех строк всех документов.Ссылка мы можем получить любой реквизит "шапки" документа (дату, номер, склад, контрагента).Проанализируем ситуацию, упомянутую в обсуждении: что если в документе пустая табличная часть? Если мы делаем запрос напрямую к Документ.ПеремещениеТоваров.Товары, то документы без строк вообще не попадут в результат запроса. Для SQL это эквивалентно операции INNER JOIN.
Если ваша задача требует обязательного наличия в выборке даже тех документов, в которых нет товаров, необходимо использовать Левое соединение. Посмотрим на пример реализации такого запроса:
Запрос.Текст =
"ВЫБРАТЬ
| Перемещение.Ссылка КАК ДокументСсылка,
| Перемещение.Дата КАК Дата,
| ЕСТЬNULL(Товары.Номенклатура, ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)) КАК Номенклатура
|ИЗ
| Документ.ПеремещениеТоваров КАК Перемещение
| ЛЕВОЕ СОЕДИНЕНИЕ Документ.ПеремещениеТоваров.Товары КАК Товары
| ПО Перемещение.Ссылка = Товары.Ссылка
|ГДЕ
| Перемещение.Ссылка В(&МассивОбъектов)";
В данном случае мы берем таблицу документов как основную и "приклеиваем" к ней строки товаров. Если строк нет, поле Номенклатура примет значение NULL, которое мы корректно обрабатываем функцией ЕСТЬNULL.
Часто для удобства обработки данных программисты используют метод РезультатЗапроса.Выгрузить(). Рассмотрим, как ведет себя система в зависимости от структуры запроса:
Товары.Номенклатура, метод Выгрузить() создаст таблицу значений, где в одной из колонок будут находиться другие таблицы значений (вложенные). Это усложняет поиск и сортировку.Выгрузить() создаст обычную "плоскую" таблицу значений. Каждая строка этой таблицы будет соответствовать одной строке из документа.Проанализируем практический совет: если вам нужно просто пересчитать общее количество товара или собрать список всей номенклатуры из пачки документов, всегда используйте запрос к таблице табличной части. Это сэкономит память сервера и упростит ваш алгоритм.
Подводя итог нашему разбору, выделим основные правила работы с табличными частями в 1С:
РезультатЗапроса.Документ.Имя.ИмяТЧ), если вам нужна плоская структура данных.Ссылка в табличной части — это мощный инструмент, позволяющий достать любые данные документа-хозяина.ЕСТЬNULL, чтобы избежать ошибок при дальнейшей обработке данных в коде.Применяя эти подходы, вы сможете писать более чистый, быстрый и отказоустойчивый код на платформе 1С.