Выборка в 1С 8.3: от конструктора запроса до обхода в коде
Содержание
Выборка в 1С 8.3 — это объект, по которому вы пробегаете строки результата запроса одну за другой. Создаётся она в два шага: сначала объект Запрос выполняет текст на языке запросов и возвращает РезультатЗапроса, затем метод Выбрать() отдаёт саму выборку, а цикл Пока Выборка.Следующий() Цикл читает строки.
Весь рабочий минимум — четыре строки кода:
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ Код, Наименование ИЗ Справочник.Номенклатура";
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Сообщить(Выборка.Наименование);
КонецЦикла;
Ниже — как собрать текст запроса в конструкторе, как обойти результат разными способами, как отфильтровать данные параметрами и отбором и что сделать, чтобы выборка не тормозила на больших таблицах.
Что такое выборка и чем она отличается от результата запроса
В цепочке участвуют три разных объекта, и их легко перепутать.
- 🧱
Запрос— контейнер для текста запроса и параметров. Сам данные не хранит. - 📦
РезультатЗапроса— то, что вернул методВыполнить(). Хранит все строки в памяти, но пробежать по ним напрямую нельзя. - 🚶
Выборка(ВыборкаИзРезультатаЗапроса) — курсор по результату. Двигается только вперёд методомСледующий().
Выборка не копирует данные — она указывает на строку внутри результата. Поэтому она лёгкая и подходит для обхода даже больших наборов. Если же вам нужна таблица в памяти, с которой удобно работать как с массивом, используйте не выборку, а выгрузку:
ТЗ = Запрос.Выполнить().Выгрузить(); // ТаблицаЗначений
Правило простое: нужно пройти строки по одному разу — берите Выбрать(). Нужно сортировать, искать, передавать таблицу в другую функцию — берите Выгрузить().
Проверить, вернул ли запрос хоть что-то, можно до обхода:
Результат = Запрос.Выполнить();
Если Результат.Пустой() Тогда
Возврат;
КонецЕсли;
Выборка = Результат.Выбрать();
Как собрать текст запроса в конструкторе
Писать текст запроса руками необязательно. В 1С есть визуальный конструктор, который сам составит синтаксически верный текст.
- Поставьте курсор в модуле на строку, где должен появиться текст (например, внутри кавычек после
Запрос.Текст =). - Нажмите правую кнопку мыши →
Конструктор запроса. Если 1С спросит создать новый запрос — согласитесь. - В левой колонке
База данныхраскройте нужную таблицу (справочник, документ, регистр) и перетащите поля в колонкуПоля. - На вкладке
Условиязадайте отбор, на вкладкеПорядок— сортировку, на вкладкеГруппировка— итоги. - Нажмите
ОК— конструктор вставит готовый текст в модуль.
Есть и второй вариант — Конструктор запроса с обработкой результата. Он доступен из того же контекстного меню и, кроме текста запроса, сразу генерирует весь код: создание объекта, выполнение и цикл обхода выборки. Для типовых обходов это экономит время.
Конструктор переписывает выделенный текст целиком. Если вы уже поправили запрос руками, повторный вызов конструктора затрёт правки — сначала выделите только ту часть, которую хотите пересобрать.
Простейший текст запроса на языке 1С выглядит так:
ВЫБРАТЬ
Номенклатура.Код КАК Код,
Номенклатура.Наименование КАК Наименование,
Номенклатура.Артикул КАК Артикул
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
НЕ Номенклатура.ПометкаУдаления
УПОРЯДОЧИТЬ ПО
Наименование
Псевдоним через КАК задаёт имя поля в выборке. Обращаться потом будете именно к псевдониму: Выборка.Код, а не Выборка.Номенклатура.Код.
Как создать выборку в коде вручную
Когда запрос зависит от условий — например, период задаёт пользователь, — текст собирают в коде, а значения передают параметрами. Никогда не вставляйте значения прямо в текст запроса склейкой строк — это и медленно, и небезопасно. Для этого есть УстановитьПараметр().
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Продажи.Номенклатура КАК Товар,
| Продажи.Количество КАК Количество,
| Продажи.Сумма КАК Сумма
|ИЗ
| РегистрНакопления.Продажи КАК Продажи
|ГДЕ
| Продажи.Период МЕЖДУ &НачалоПериода И &КонецПериода";
Запрос.УстановитьПараметр("НачалоПериода", НачалоДня(ДатаС));
Запрос.УстановитьПараметр("КонецПериода", КонецДня(ДатаПо));
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
ОбщаяСумма = ОбщаяСумма + Выборка.Сумма;
КонецЦикла;
Вертикальная черта | в начале строк — это разделитель многострочного текста, 1С добавляет её автоматически при переносе. Параметры в тексте помечаются амперсандом (&НачалоПериода), а в коде задаются без него.
К полям выборки обращаются по имени как к свойствам объекта (Выборка.Сумма) либо по строке — это удобно, когда имя поля заранее неизвестно:
ИмяПоля = "Сумма";
Значение = Выборка[ИмяПоля];
Выборка без запроса: метод Выбрать у менеджера объекта
Запрос — не единственный способ получить выборку. У менеджера любого справочника, документа или перечисления есть собственный метод Выбрать(), который сразу возвращает ВыборкаСправочника или ВыборкаДокумента — курсор по элементам базы.
Выборка = Справочники.Контрагенты.Выбрать();
Пока Выборка.Следующий() Цикл
Сообщить(Выборка.Наименование + " / " + Выборка.ИНН);
КонецЦикла;
Такая выборка удобна для разовых обходов и фоновых обработок, когда не нужен сложный отбор или соединение таблиц. У неё есть специализированные варианты:
ВыбратьИерархически()— обход с учётом групп справочника, сначала группа, затем её содержимое.Выбрать(Родитель)— только элементы внутри указанной группы.ВыбратьПоВладельцу(Владелец)— для подчинённых справочников (например, договоры конкретного контрагента).
Отбор задают структурой в параметре Отбор:
Отбор = Новый Структура("ЮрФизЛицо", ПредопределённоеЗначение("Перечисление.ЮрФизЛицо.ЮрЛицо"));
Выборка = Справочники.Контрагенты.Выбрать(, , Отбор);
Отбор у менеджера работает только по индексированным реквизитам и измерениям. Для фильтра по произвольному полю или по нескольким условиям сразу надёжнее запрос с секцией ГДЕ — он и быстрее, и не ограничен индексами.
Каждая строка такой выборки — это, по сути, объект базы: доступны все его реквизиты и табличные части, а Выборка.Ссылка даёт ссылку для записи или проведения.
Как обойти выборку по группировкам и иерархии
Если в запросе есть ИТОГИ ... ПО, результат становится многоуровневым, и по нему можно идти не только построчно, но и по группам. Режим обхода задаётся параметром метода Выбрать().
| Режим обхода | Что получаем |
|---|---|
Выбрать() (по умолчанию) | Все детальные строки подряд, итоги пропускаются |
Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам) | Сначала строки-итоги верхнего уровня |
Выбрать(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией) | Итоги с учётом иерархии справочника |
Чтобы спуститься с уровня итога на детальные строки, из текущей выборки берут вложенную:
ВыборкаИтоги = Запрос.Выполнить().Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаИтоги.Следующий() Цикл
Сообщить("Склад: " + ВыборкаИтоги.Склад + ", итого: " + ВыборкаИтоги.Сумма);
ВыборкаСтроки = ВыборкаИтоги.Выбрать(); // детальные строки этой группы
Пока ВыборкаСтроки.Следующий() Цикл
Сообщить(" " + ВыборкаСтроки.Товар + " — " + ВыборкаСтроки.Сумма);
КонецЦикла;
КонецЦикла;
Такой двухуровневый обход — основа печати отчётов с подытогами: внешний цикл даёт шапку группы, внутренний — её содержимое.
Как отфильтровать и дополнительно обработать данные
Основной фильтр всегда делайте в тексте запроса, в секции ГДЕ — база отберёт нужное быстрее, чем код. Но у выборки есть и свои приёмы постобработки.
Отбор по значению прямо на выборке результата (без повторного запроса к базе):
Результат = Запрос.Выполнить();
Отбор = Новый Структура("Склад", ВыбранныйСклад);
Выборка = Результат.Выбрать(ОбходРезультатаЗапроса.Прямой, "Склад", Отбор);
Полезные методы самой выборки:
- 🔢
Выборка.Количество()— число строк, не обходя их циклом. - 🔄
Выборка.Сбросить()— вернуть курсор в начало, чтобы пройти результат второй раз. - 🔗
Выборка.Владелец()— родительская строка при вложенном обходе.
Когда из результата нужен не обход, а один столбец значений (список товаров, массив ссылок для дальнейшего отбора), выгружать и перебирать его вручную не нужно — есть короткий путь:
МассивТоваров = Запрос.Выполнить().Выгрузить().ВыгрузитьКолонку("Товар");
Полученный массив можно сразу передать параметром в следующий запрос через условие В (&МассивТоваров) — это стандартный приём, когда данные собираются в два этапа.
Если по строке выборки нужно изменить сам объект (например, перепровести документ ), выбирайте в запросе ссылку, а объект получайте уже из неё:
Пока Выборка.Следующий() Цикл
ОбъектДокумента = Выборка.Ссылка.ПолучитьОбъект();
ОбъектДокумента.Комментарий = "Проверено";
ОбъектДокумента.Записать();
КонецЦикла;
Запись объекта внутри обхода большой выборки может длиться долго и блокировать данные. Для массовых операций разбивайте работу на порции и по возможности выполняйте в фоновом задании.
Как ускорить выборку на больших таблицах
Выборка тормозит почти всегда не из-за обхода, а из-за самого запроса. Разберём главные рычаги.
Не выполняйте запрос внутри цикла. Это причина номер один медленных обработок. Если для каждой строки делается отдельный запрос, тысяча строк — это тысяча обращений к базе.
Так делать не нужно — для каждого товара идёт отдельный запрос за ценой:
Пока ВыборкаТоваров.Следующий() Цикл
ЗапросЦены = Новый Запрос("ВЫБРАТЬ Цена ИЗ ... ГДЕ Товар = &Товар");
ЗапросЦены.УстановитьПараметр("Товар", ВыборкаТоваров.Товар);
// медленно: один запрос на каждую строку
КонецЦикла;
Правильно — соединить товары с ценами в одном запросе через ЛЕВОЕ СОЕДИНЕНИЕ и обойти результат один раз. Цена придёт сразу полем выборки (ВыборкаТоваров.Цена), и обращений к базе будет ровно одно.
Выбирайте только нужные поля. Запрос вида ВЫБРАТЬ * тянет лишние колонки и мешает серверу использовать индексы. Перечисляйте поля явно.
Опирайтесь на индексированные поля. В секции ГДЕ и в соединениях используйте поля, по которым есть индекс: измерения регистров, реквизиты с индексированием, ссылки. Фильтр по неиндексированному реквизиту заставляет базу читать таблицу целиком.
Ограничивайте объём. Если нужны не все строки, добавьте ВЫБРАТЬ ПЕРВЫЕ 100.
Временные таблицы с индексом. Когда в запросе есть промежуточный набор, который соединяется с большими таблицами, выносите его во временную таблицу через ПОМЕСТИТЬ и индексируйте поля соединения (ИНДЕКСИРОВАТЬ ПО). Для нескольких связанных запросов используйте менеджер временных таблиц и пакет:
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
РезультатыПакета = Запрос.ВыполнитьПакет();
Чек-лист перед тем, как сдавать обработку в работу:
Соединение полей через точку в условии (Товар.ЕдиницаИзмерения.Код) незаметно порождает неявные соединения таблиц. На больших объёмах их лучше заменять явным ЛЕВОЕ СОЕДИНЕНИЕ.
Типичные ошибки при работе с выборкой
Эти сообщения встречаются чаще всего — и у каждого понятная причина.
- «Поле объекта не обнаружено (Наименование)». В выборке нет такого поля. Либо опечатка, либо вы обращаетесь к имени из базы, а не к псевдониму из
КАК. Смотрите, как поле названо в тексте запроса. - Пустые значения там, где ждали данные. Курсор ещё не сдвинут: обращение к
Выборка.Полеимеет смысл только после первогоВыборка.Следующий(). - Цикл не выполняется ни разу. Запрос ничего не вернул. Проверьте условия
ГДЕи значения параметров — частая причина, что параметр не установлен и подставилсяNULL. - «Выборка из результата запроса уже пройдена». Вы пытаетесь обойти выборку второй раз. Либо вызовите
Сбросить(), либо получите новую выборку из результата:Результат.Выбрать(). - Запрос работает в конфигураторе, но падает у пользователя. Почти всегда — права доступа
. В режиме
RLSпользователь видит не все строки; проверяйте через роль этого пользователя.
Отдельно про NULL. Поля, которых может не быть (левое соединение без совпадения, пустой реквизит), приходят как NULL, а не как ноль или пустая строка. Чтобы не ловить ошибки в арифметике, оборачивайте такие поля в ЕСТЬNULL:
ЕСТЬNULL(Остатки.КоличествоОстаток, 0) КАК Остаток
Частые вопросы
Чем `Выбрать()` отличается от `Выгрузить()`?
Выбрать() даёт курсор для однократного обхода вперёд и почти не расходует память. Выгрузить() строит ТаблицаЗначений целиком — по ней можно сортировать, искать строки и передавать её в другие процедуры. Для простого перебора берите Выбрать(), для дальнейшей обработки набора — Выгрузить().Можно ли пройти одну выборку дважды?
Выборка.Сбросить() перед вторым проходом или получите новую выборку из того же РезультатЗапроса методом Выбрать(). Сам результат запроса при этом повторно к базе не обращается.Как узнать число строк, не обходя выборку циклом?
Выборка.Количество() — он вернёт количество строк текущего уровня сразу, без перебора.Нужно ли закрывать или удалять выборку после обхода?
Как из строки выборки изменить сам объект базы?
Ссылка, а в цикле вызовите Выборка.Ссылка.ПолучитьОбъект(), измените реквизиты и вызовите Записать(). Напрямую менять данные через выборку нельзя — она доступна только на чтение.Почему не срабатывает отбор у выборки менеджера?
Справочники.Имя.Выбрать() применяется только к индексированным реквизитам и измерениям, а условие в нём всегда строгое равенство. Если нужен диапазон, сравнение «не равно» или отбор по обычному реквизиту — замените выборку менеджера на запрос с секцией ГДЕ.