Выборка в 1С 8.3: от конструктора запроса до обхода в коде

· 9 мин

Содержание

Выборка в 1С 8.3 — это объект, по которому вы пробегаете строки результата запроса одну за другой. Создаётся она в два шага: сначала объект Запрос выполняет текст на языке запросов и возвращает РезультатЗапроса, затем метод Выбрать() отдаёт саму выборку, а цикл Пока Выборка.Следующий() Цикл читает строки.

Весь рабочий минимум — четыре строки кода:

Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ Код, Наименование ИЗ Справочник.Номенклатура";
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
    Сообщить(Выборка.Наименование);
КонецЦикла;

Ниже — как собрать текст запроса в конструкторе, как обойти результат разными способами, как отфильтровать данные параметрами и отбором и что сделать, чтобы выборка не тормозила на больших таблицах.

Что такое выборка и чем она отличается от результата запроса

В цепочке участвуют три разных объекта, и их легко перепутать.

  • 🧱 Запрос — контейнер для текста запроса и параметров. Сам данные не хранит.
  • 📦 РезультатЗапроса — то, что вернул метод Выполнить(). Хранит все строки в памяти, но пробежать по ним напрямую нельзя.
  • 🚶 Выборка (ВыборкаИзРезультатаЗапроса) — курсор по результату. Двигается только вперёд методом Следующий().

Выборка не копирует данные — она указывает на строку внутри результата. Поэтому она лёгкая и подходит для обхода даже больших наборов. Если же вам нужна таблица в памяти, с которой удобно работать как с массивом, используйте не выборку, а выгрузку:

ТЗ = Запрос.Выполнить().Выгрузить(); // ТаблицаЗначений

Правило простое: нужно пройти строки по одному разу — берите Выбрать(). Нужно сортировать, искать, передавать таблицу в другую функцию — берите Выгрузить().

Проверить, вернул ли запрос хоть что-то, можно до обхода:

Результат = Запрос.Выполнить();
Если Результат.Пустой() Тогда
    Возврат;
КонецЕсли;
Выборка = Результат.Выбрать();

Как собрать текст запроса в конструкторе

Писать текст запроса руками необязательно. В 1С есть визуальный конструктор, который сам составит синтаксически верный текст.

  1. Поставьте курсор в модуле на строку, где должен появиться текст (например, внутри кавычек после Запрос.Текст =).
  2. Нажмите правую кнопку мыши → Конструктор запроса. Если 1С спросит создать новый запрос — согласитесь.
  3. В левой колонке База данных раскройте нужную таблицу (справочник, документ, регистр) и перетащите поля в колонку Поля.
  4. На вкладке Условия задайте отбор, на вкладке Порядок — сортировку, на вкладке Группировка — итоги.
  5. Нажмите ОК — конструктор вставит готовый текст в модуль.

Есть и второй вариант — Конструктор запроса с обработкой результата. Он доступен из того же контекстного меню и, кроме текста запроса, сразу генерирует весь код: создание объекта, выполнение и цикл обхода выборки. Для типовых обходов это экономит время.

Конструктор переписывает выделенный текст целиком. Если вы уже поправили запрос руками, повторный вызов конструктора затрёт правки — сначала выделите только ту часть, которую хотите пересобрать.

Простейший текст запроса на языке 1С выглядит так:

ВЫБРАТЬ
    Номенклатура.Код КАК Код,
    Номенклатура.Наименование КАК Наименование,
    Номенклатура.Артикул КАК Артикул
ИЗ
    Справочник.Номенклатура КАК Номенклатура
ГДЕ
    НЕ Номенклатура.ПометкаУдаления
УПОРЯДОЧИТЬ ПО
    Наименование

Псевдоним через КАК задаёт имя поля в выборке. Обращаться потом будете именно к псевдониму: Выборка.Код, а не Выборка.Номенклатура.Код.

Как создать выборку в коде вручную

Когда запрос зависит от условий — например, период задаёт пользователь, — текст собирают в коде, а значения передают параметрами. Никогда не вставляйте значения прямо в текст запроса склейкой строк — это и медленно, и небезопасно. Для этого есть УстановитьПараметр().

Запрос = Новый Запрос;
Запрос.Текст =
    "ВЫБРАТЬ
    |   Продажи.Номенклатура КАК Товар,
    |   Продажи.Количество КАК Количество,
    |   Продажи.Сумма КАК Сумма
    |ИЗ
    |   РегистрНакопления.Продажи КАК Продажи
    |ГДЕ
    |   Продажи.Период МЕЖДУ &НачалоПериода И &КонецПериода";

Запрос.УстановитьПараметр("НачалоПериода", НачалоДня(ДатаС));
Запрос.УстановитьПараметр("КонецПериода", КонецДня(ДатаПо));

Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
    ОбщаяСумма = ОбщаяСумма + Выборка.Сумма;
КонецЦикла;

Вертикальная черта | в начале строк — это разделитель многострочного текста, 1С добавляет её автоматически при переносе. Параметры в тексте помечаются амперсандом (&НачалоПериода), а в коде задаются без него.

К полям выборки обращаются по имени как к свойствам объекта (Выборка.Сумма) либо по строке — это удобно, когда имя поля заранее неизвестно:

ИмяПоля = "Сумма";
Значение = Выборка[ИмяПоля];

Выборка без запроса: метод Выбрать у менеджера объекта

Запрос — не единственный способ получить выборку. У менеджера любого справочника, документа или перечисления есть собственный метод Выбрать(), который сразу возвращает ВыборкаСправочника или ВыборкаДокумента — курсор по элементам базы.

Выборка = Справочники.Контрагенты.Выбрать();
Пока Выборка.Следующий() Цикл
    Сообщить(Выборка.Наименование + " / " + Выборка.ИНН);
КонецЦикла;

Такая выборка удобна для разовых обходов и фоновых обработок, когда не нужен сложный отбор или соединение таблиц. У неё есть специализированные варианты:

  • ВыбратьИерархически() — обход с учётом групп справочника, сначала группа, затем её содержимое.
  • Выбрать(Родитель) — только элементы внутри указанной группы.
  • ВыбратьПоВладельцу(Владелец) — для подчинённых справочников (например, договоры конкретного контрагента).

Отбор задают структурой в параметре Отбор:

Отбор = Новый Структура("ЮрФизЛицо", ПредопределённоеЗначение("Перечисление.ЮрФизЛицо.ЮрЛицо"));
Выборка = Справочники.Контрагенты.Выбрать(, , Отбор);

Отбор у менеджера работает только по индексированным реквизитам и измерениям. Для фильтра по произвольному полю или по нескольким условиям сразу надёжнее запрос с секцией ГДЕ — он и быстрее, и не ограничен индексами.

Каждая строка такой выборки — это, по сути, объект базы: доступны все его реквизиты и табличные части, а Выборка.Ссылка даёт ссылку для записи или проведения.

Как обойти выборку по группировкам и иерархии

Если в запросе есть ИТОГИ ... ПО, результат становится многоуровневым, и по нему можно идти не только построчно, но и по группам. Режим обхода задаётся параметром метода Выбрать().

Режим обходаЧто получаем
Выбрать() (по умолчанию)Все детальные строки подряд, итоги пропускаются
Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам)Сначала строки-итоги верхнего уровня
Выбрать(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией)Итоги с учётом иерархии справочника

Чтобы спуститься с уровня итога на детальные строки, из текущей выборки берут вложенную:

ВыборкаИтоги = Запрос.Выполнить().Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаИтоги.Следующий() Цикл
    Сообщить("Склад: " + ВыборкаИтоги.Склад + ", итого: " + ВыборкаИтоги.Сумма);
    ВыборкаСтроки = ВыборкаИтоги.Выбрать(); // детальные строки этой группы
    Пока ВыборкаСтроки.Следующий() Цикл
        Сообщить("   " + ВыборкаСтроки.Товар + " — " + ВыборкаСтроки.Сумма);
    КонецЦикла;
КонецЦикла;

Такой двухуровневый обход — основа печати отчётов с подытогами: внешний цикл даёт шапку группы, внутренний — её содержимое.

Как отфильтровать и дополнительно обработать данные

Основной фильтр всегда делайте в тексте запроса, в секции ГДЕ — база отберёт нужное быстрее, чем код. Но у выборки есть и свои приёмы постобработки.

Отбор по значению прямо на выборке результата (без повторного запроса к базе):

Результат = Запрос.Выполнить();
Отбор = Новый Структура("Склад", ВыбранныйСклад);
Выборка = Результат.Выбрать(ОбходРезультатаЗапроса.Прямой, "Склад", Отбор);

Полезные методы самой выборки:

  • 🔢 Выборка.Количество() — число строк, не обходя их циклом.
  • 🔄 Выборка.Сбросить() — вернуть курсор в начало, чтобы пройти результат второй раз.
  • 🔗 Выборка.Владелец() — родительская строка при вложенном обходе.

Когда из результата нужен не обход, а один столбец значений (список товаров, массив ссылок для дальнейшего отбора), выгружать и перебирать его вручную не нужно — есть короткий путь:

МассивТоваров = Запрос.Выполнить().Выгрузить().ВыгрузитьКолонку("Товар");

Полученный массив можно сразу передать параметром в следующий запрос через условие В (&МассивТоваров) — это стандартный приём, когда данные собираются в два этапа.

Если по строке выборки нужно изменить сам объект (например, перепровести документ ), выбирайте в запросе ссылку, а объект получайте уже из неё:

Пока Выборка.Следующий() Цикл
    ОбъектДокумента = Выборка.Ссылка.ПолучитьОбъект();
    ОбъектДокумента.Комментарий = "Проверено";
    ОбъектДокумента.Записать();
КонецЦикла;

Запись объекта внутри обхода большой выборки может длиться долго и блокировать данные. Для массовых операций разбивайте работу на порции и по возможности выполняйте в фоновом задании.

Как ускорить выборку на больших таблицах

Выборка тормозит почти всегда не из-за обхода, а из-за самого запроса. Разберём главные рычаги.

Не выполняйте запрос внутри цикла. Это причина номер один медленных обработок. Если для каждой строки делается отдельный запрос, тысяча строк — это тысяча обращений к базе.

Так делать не нужно — для каждого товара идёт отдельный запрос за ценой:

Пока ВыборкаТоваров.Следующий() Цикл
    ЗапросЦены = Новый Запрос("ВЫБРАТЬ Цена ИЗ ... ГДЕ Товар = &Товар");
    ЗапросЦены.УстановитьПараметр("Товар", ВыборкаТоваров.Товар);
    // медленно: один запрос на каждую строку
КонецЦикла;

Правильно — соединить товары с ценами в одном запросе через ЛЕВОЕ СОЕДИНЕНИЕ и обойти результат один раз. Цена придёт сразу полем выборки (ВыборкаТоваров.Цена), и обращений к базе будет ровно одно.

Выбирайте только нужные поля. Запрос вида ВЫБРАТЬ * тянет лишние колонки и мешает серверу использовать индексы. Перечисляйте поля явно.

Опирайтесь на индексированные поля. В секции ГДЕ и в соединениях используйте поля, по которым есть индекс: измерения регистров, реквизиты с индексированием, ссылки. Фильтр по неиндексированному реквизиту заставляет базу читать таблицу целиком.

Ограничивайте объём. Если нужны не все строки, добавьте ВЫБРАТЬ ПЕРВЫЕ 100.

Временные таблицы с индексом. Когда в запросе есть промежуточный набор, который соединяется с большими таблицами, выносите его во временную таблицу через ПОМЕСТИТЬ и индексируйте поля соединения (ИНДЕКСИРОВАТЬ ПО). Для нескольких связанных запросов используйте менеджер временных таблиц и пакет:

Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
РезультатыПакета = Запрос.ВыполнитьПакет();

Чек-лист перед тем, как сдавать обработку в работу:

Проверка запроса на производительность
Выполнено: 0 / 6

Соединение полей через точку в условии (Товар.ЕдиницаИзмерения.Код) незаметно порождает неявные соединения таблиц. На больших объёмах их лучше заменять явным ЛЕВОЕ СОЕДИНЕНИЕ.

Типичные ошибки при работе с выборкой

Эти сообщения встречаются чаще всего — и у каждого понятная причина.

  • «Поле объекта не обнаружено (Наименование)». В выборке нет такого поля. Либо опечатка, либо вы обращаетесь к имени из базы, а не к псевдониму из КАК. Смотрите, как поле названо в тексте запроса.
  • Пустые значения там, где ждали данные. Курсор ещё не сдвинут: обращение к Выборка.Поле имеет смысл только после первого Выборка.Следующий().
  • Цикл не выполняется ни разу. Запрос ничего не вернул. Проверьте условия ГДЕ и значения параметров — частая причина, что параметр не установлен и подставился NULL.
  • «Выборка из результата запроса уже пройдена». Вы пытаетесь обойти выборку второй раз. Либо вызовите Сбросить(), либо получите новую выборку из результата: Результат.Выбрать().
  • Запрос работает в конфигураторе, но падает у пользователя. Почти всегда — права доступа . В режиме RLS пользователь видит не все строки; проверяйте через роль этого пользователя.

Отдельно про NULL. Поля, которых может не быть (левое соединение без совпадения, пустой реквизит), приходят как NULL, а не как ноль или пустая строка. Чтобы не ловить ошибки в арифметике, оборачивайте такие поля в ЕСТЬNULL:

ЕСТЬNULL(Остатки.КоличествоОстаток, 0) КАК Остаток

Частые вопросы

Чем `Выбрать()` отличается от `Выгрузить()`?
Выбрать() даёт курсор для однократного обхода вперёд и почти не расходует память. Выгрузить() строит ТаблицаЗначений целиком — по ней можно сортировать, искать строки и передавать её в другие процедуры. Для простого перебора берите Выбрать(), для дальнейшей обработки набора — Выгрузить().
Можно ли пройти одну выборку дважды?
Напрямую нет — курсор движется только вперёд. Вызовите Выборка.Сбросить() перед вторым проходом или получите новую выборку из того же РезультатЗапроса методом Выбрать(). Сам результат запроса при этом повторно к базе не обращается.
Как узнать число строк, не обходя выборку циклом?
Используйте метод Выборка.Количество() — он вернёт количество строк текущего уровня сразу, без перебора.
Нужно ли закрывать или удалять выборку после обхода?
Нет. Специально закрывать объект не требуется — он освобождается автоматически, когда на него не остаётся ссылок. Достаточно не держать результат запроса в переменной дольше, чем он нужен.
Как из строки выборки изменить сам объект базы?
Выберите в запросе поле Ссылка, а в цикле вызовите Выборка.Ссылка.ПолучитьОбъект(), измените реквизиты и вызовите Записать(). Напрямую менять данные через выборку нельзя — она доступна только на чтение.
Почему не срабатывает отбор у выборки менеджера?
Отбор у Справочники.Имя.Выбрать() применяется только к индексированным реквизитам и измерениям, а условие в нём всегда строгое равенство. Если нужен диапазон, сравнение «не равно» или отбор по обычному реквизиту — замените выборку менеджера на запрос с секцией ГДЕ.