Каждый начинающий разработчик в среде «1С:Предприятие 8» рано или поздно сталкивается с удивительным феноменом. В конфигурации создается простейший динамический список, в его схему компоновки или текст запроса вставляется элементарный выбор полей (например, получение номеров документов), и на форме отображаются сотни записей. Но стоит написать абсолютно аналогичный запрос программно в модуле, запустить отладчик и посмотреть на результат — и система возвращает пустоту, Неопределено или странное значение, которое разработчик интерпретирует как NULL.
Давайте подробно разберем, почему возникает эта иллюзия «магии» СУБД, чем принципиально отличается поведение динамического списка от программного запроса, как правильно обрабатывать полученные данные в коде и как не дать отладчику ввести себя в заблуждение.
Прежде всего, проанализируем архитектурные различия этих двух механизмов.
Динамический список (объект типа ДинамическийСписок) — это специализированный высокоуровневый объект платформы, предназначенный для отображения списков данных в интерфейсе. Когда вы привязываете запрос к динамическому списку, платформа берет на себя всю тяжелую работу «под капотом»:
Программный запрос (объект типа Запрос) — это низкоуровневый инструмент прямого обращения к данным. Когда мы пишем код программного выполнения запроса, мы обязуемся самостоятельно управлять всем жизненным циклом данных:
Новый Запрос.Текст.УстановитьПараметр().Выполнить().---
Выясним причину, по которой автор темы на форуме увидел значение NULL (или Неопределено) в окне «Табло» отладчика после выполнения кода:
Рез = Запрос.Выполнить().Выгрузить();
Здесь кроется фундаментальная ошибка работы с типами данных в отладке 1С. Метод Выгрузить() возвращает коллекцию типа ТаблицаЗначений. Если вы просто добавите переменную Рез в Табло отладчика, вы увидите текстовое описание типа или пустую строку, так как вся таблица значений не может отобразиться в одной строчке текстового поля Табло.
Если же вы попытаетесь обратиться напрямую к полям выборки, которая еще не была инициализирована методом Следующий(), например, написав в Табло Выборка.Номер, система выдаст ошибку чтения значения или вернет Неопределено. Разработчик-новичок часто путает это состояние с отсутствием записей в базе данных или системным значением NULL.
Посмотрим подробнее, как правильно работать с результатами выполнения запроса в коде.
---
Если нам необходимо программно перебрать каждую запись, полученную из базы данных, мы должны использовать объект ВыборкаИзРезультатаЗапроса. По умолчанию, когда мы вызываем метод Выбрать(), указатель выборки стоит в позиции перед первой записью. Чтобы начать чтение данных, необходимо сдвинуть указатель вперед с помощью метода Следующий(). Этот метод возвращает значение Истина, если впереди есть строка с данными, и Ложь, если записи закончились.
Рассмотрим пример правильного кода с использованием цикла:
// 1. Инициализируем запрос
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ДЗ.Номер КАК НомерЗаказа,
| ДЗ.Дата КАК ДатаЗаказа
|ИЗ
| Документ.ЗаказКлиента КАК ДЗ";
// 2. Выполняем запрос на СУБД и получаем РезультатЗапроса
РезультатЗапроса = Запрос.Выполнить();
// Проверяем, есть ли вообще данные в результате, чтобы не тратить ресурсы
Если Не РезультатЗапроса.Пустой() Тогда
// 3. Создаем выборку для обхода
Выборка = РезультатЗапроса.Выбрать();
// 4. Запускаем цикл обхода записей
Пока Выборка.Следующий() Цикл
// Теперь мы можем обратиться к конкретному полю текущей строки
Сообщить("Обрабатывается заказ № " + Выборка.НомерЗаказа + " от " + Формат(Выборка.ДатаЗаказа, "ДФ=dd.MM.yyyy"));
КонецЦикла;
КонецЕсли;
Обратите внимание на использование метода РезультатЗапроса.Пустой(). Это быстрая и легковесная операция, которая позволяет моментально узнать, вернула ли база данных хоть что-то, без выделения памяти под обход элементов.
---
В некоторых случаях нам удобнее сразу получить всю таблицу данных в оперативной памяти сервера, чтобы передать ее в другой метод, заполнить реквизит формы или быстро проверить количество строк. Для этого используется метод Выгрузить(), возвращающий ТаблицуЗначений.
Проанализируем пример кода:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ДЗ.Ссылка КАК Ссылка,
| ДЗ.Номер КАК Номер
|ИЗ
| Документ.ЗаказКлиента КАК ДЗ";
ТаблицаРезультатов = Запрос.Выполнить().Выгрузить();
// Получаем количество строк
КоличествоСтрок = ТаблицаРезультатов.Количество();
Сообщить("Всего выгружено документов: " + КоличествоСтрок);
Важный лайфхак для работы в отладчике: если вы остановили выполнение кода точкой останова на строке после выгрузки таблицы и хотите воочию увидеть, какие данные туда попали:
ТаблицаРезультатов в тексте модуля или добавьте её в Табло отладчика.---
Платформа 1С:Предприятие предоставляет мощный инструмент автоматической генерации шаблонного кода, который полностью исключает опечатки и логические ошибки при обходе выборки. Это инструмент «Конструктор запроса с обработкой результата».
Разберем по шагам, как им воспользоваться:
Пока Выборка.Следующий().Документ.ЗаказКлиента выберите поле Номер).Платформа автоматически сгенерирует готовый безошибочный блок кода с правильным объявлением переменных, текстом запроса, вызовом методов исполнения и готовым циклом обхода. Вам останется только вписать логику обработки внутри цикла.
---
Давайте обратим внимание на код автора из сообщения №12:
Процедура ЗаполнениеТаблицы() Экспорт
Запрос = Новый Запрос;
Документы.ЗаказКлиента.ПолучитьСсылку(); // Что это?
Запрос.Текст = ...
КонецПроцедуры
В этой процедуре присутствует абсолютно бессмысленная и вредная строка: Документы.ЗаказКлиента.ПолучитьСсылку();. Метод менеджера документа ПолучитьСсылку() генерирует новый уникальный идентификатор (GUID) в рамках СУБД и возвращает пустую ссылку на документ этого типа.
Поскольку результат работы этого метода никуда не записывается (нет присвоения переменной вида НоваяСсылка = Документы...), эта строка выполняется «в пустоту». Она бесполезно тратит системные ресурсы на генерацию UUID и засоряет код программного модуля. Старайтесь избегать подобных конструкций при написании процедур.
---
В сообщении №1 один из участников обсуждения задал критически важный вопрос: «А сколько доков в базе? Если база рабочая и пухлая, то зачем все собирать-то, тормозить же начнет?».
Это классическое архитектурное правило разработки в 1С. Программный запрос без ограничений (без конструкции ГДЕ или без использования ключевого слова ПЕРВЫЕ) на пустой тестовой базе с 5–10 документами выполнится мгновенно. Но если этот же код запустить в рабочей базе крупного предприятия, где хранятся миллионы заказов клиентов, произойдет следующее:
Поэтому, если вам нужно программно прочитать данные, всегда используйте точные отборы по периоду, организации или пометке удаления в секции ГДЕ вашего запроса, либо ограничивайте выборку, если вам нужны только свежие записи:
// Пример запроса только по актуальным и проведенным заказам за конкретный период
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 100
| ДЗ.Ссылка КАК Ссылка,
| ДЗ.Номер КАК Номер
|ИЗ
| Документ.ЗаказКлиента КАК ДЗ
|ГДЕ
| ДЗ.Проведен = ИСТИНА
| И ДЗ.Дата МЕЖДУ &НачалоПериода И &КонецПериода";
Запрос.УстановитьПараметр("НачалоПериода", НачалоДня(ТекущаяДата()));
Запрос.УстановитьПараметр("КонецПериода", КонецДня(ТекущаяДата()));
---
Для закрепления материала сформулируем основные правила работы с результатами запросов:
РезультатЗапроса или пустая Выборка в отладчике не покажут данные строк. Используйте метод Выборка.Следующий() в цикле.