Решение задач 1С:Предприятие 8 на примерах

Запросы в 1С: как написать запрос, передать параметры и обработать результат

ЗапросыОпубликовано

Запросы — основной способ получать данные из базы 1С. В статье — рабочий пример запроса с параметрами, разница между Выбрать() и Выгрузить(), пакетные запросы с временными таблицами, отладка в консоли запросов и ошибки, из-за которых код становится медленным.

Задача: получить данные из базы одним запросом

Типичная ситуация: нужно выбрать документы реализации за период по конкретному контрагенту, посчитать сумму и что-то сделать с каждой строкой — заполнить табличную часть, сформировать сообщение, передать данные дальше. Перебирать документы через Документы.РеализацияТоваровУслуг.Выбрать() и читать каждый объект — медленно и неудобно. Правильный путь — запрос: СУБД сама отберёт, отсортирует и сгруппирует данные, а код получит уже готовый результат.

Работа с запросами в 1С всегда идёт по одной схеме из четырёх шагов: создать объект Запрос, задать текст на языке запросов, установить параметры, выполнить и обработать результат. Ниже — каждый шаг с рабочим кодом, затем пакетные запросы, временные таблицы и отладка.

Базовый пример запроса 1С

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

&НаСервере
Процедура ЗаполнитьПоКонтрагентуНаСервере()

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

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

    РезультатЗапроса = Запрос.Выполнить();

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

    Выборка = РезультатЗапроса.Выбрать();
    Пока Выборка.Следующий() Цикл
        Сообщить(Строка(Выборка.Документ) + " — " + Формат(Выборка.Сумма, "ЧДЦ=2"));
    КонецЦикла;

КонецПроцедуры

Многострочный текст запроса записывается как одна строковая константа: каждая следующая строка начинается с вертикальной черты |. Псевдонимы через КАК лучше задавать всегда — по ним вы будете обращаться к полям выборки, а конструктор запроса при повторном открытии их сохранит.

Параметры запроса 1С

Всё, что меняется от вызова к вызову, передаётся параметрами: в тексте пишется имя с амперсандом (&Контрагент), а значение задаётся методом УстановитьПараметр(). Так запрос остаётся читаемым, платформа сама приводит типы, а план запроса в СУБД может переиспользоваться.

Параметром можно передать почти что угодно: ссылку, дату, число, строку, Массив или СписокЗначений для условия В, таблицу значений как источник данных, а также Граница или МоментВремени для виртуальных таблиц регистров.

СписокСкладов = Новый Массив;
СписокСкладов.Добавить(Справочники.Склады.Основной);
СписокСкладов.Добавить(Справочники.Склады.Розничный);

Запрос.Текст =
"ВЫБРАТЬ
|    Остатки.Номенклатура КАК Номенклатура,
|    Остатки.КоличествоОстаток КАК Остаток
|ИЗ
|    РегистрНакопления.ТоварыНаСкладах.Остатки(&Дата, Склад В (&Склады)) КАК Остатки";

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

Если параметр объявлен в тексте, но не установлен, выполнение завершится ошибкой «Не задано значение параметра». Проверить, какие параметры ожидает запрос, можно через Запрос.НайтиПараметры() — метод возвращает их описание без выполнения.

Условие в запросе 1С: ГДЕ, В, ПОДОБНО и другие

Отбор записей задаётся в секции ГДЕ. Чаще всего встречаются такие конструкции:

  • Поле = &Значение, <>, >, <= — обычные сравнения;
  • Поле В (&Массив) — вхождение в список; для справочников с иерархией есть В ИЕРАРХИИ, который включает подчинённые элементы;
  • Поле МЕЖДУ &Начало И &Конец — диапазон, границы включаются;
  • Наименование ПОДОБНО "%" + &Строка + "%" — поиск по подстроке;
  • Поле ЕСТЬ NULL — проверка результата левого соединения, когда парной записи нет;
  • Регистратор ССЫЛКА Документ.ПоступлениеТоваровУслуг — проверка типа составного поля.

Условия объединяются через И, ИЛИ, НЕ и скобки. Учтите, что ИЛИ по разным полям часто мешает СУБД использовать индексы — иногда быстрее разбить такой запрос на два и объединить через ОБЪЕДИНИТЬ ВСЕ. Условия на сгруппированные значения (например, сумма больше нуля) пишутся не в ГДЕ, а в ИМЕЮЩИЕ.

Результат запроса 1С: Выбрать или Выгрузить

Метод Запрос.Выполнить() возвращает объект РезультатЗапроса. Сам по себе он данных не показывает — из него получают либо выборку, либо таблицу значений.

Обход результата запроса через выборку

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

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

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

Выгрузить результат запроса в таблицу значений

Выгрузить() сразу загружает все строки в ТаблицаЗначений. Это нужно, когда данные придётся искать, сортировать, сворачивать, передавать в другую процедуру или загружать в табличную часть. Для одной колонки удобно Выгрузить().ВыгрузитьКолонку("Документ") — получится массив.

ТаблицаДокументов = Запрос.Выполнить().Выгрузить();
Объект.Товары.Загрузить(ТаблицаДокументов);

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

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

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

Если данные готовятся в несколько этапов, тексты запросов объединяют в пакет через точку с запятой. Промежуточные результаты складывают во временные таблицы конструкцией ПОМЕСТИТЬ, а следующие запросы пакета читают их как обычные источники.

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

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

// Выполнить() возвращает результат последнего запроса пакета
ТаблицаСумм = Запрос.Выполнить().Выгрузить();

// ВыполнитьПакет() возвращает массив результатов всех запросов
Результаты = Запрос.ВыполнитьПакет();
ПоследнийРезультат = Результаты[Результаты.ВГраница()];

Явный МенеджерВременныхТаблиц нужен, когда временные таблицы должны пережить один вызов Выполнить(): например, первая часть пакета формируется в одной процедуре, а другой запрос с тем же менеджером использует её позже. Без менеджера временные таблицы живут только в пределах пакета. Когда таблицы больше не нужны, менеджер закрывают методом Закрыть() или удаляют таблицу запросом УНИЧТОЖИТЬ ВТ_Документы.

Для запросов уничтожения и ПОМЕСТИТЬ результат в массиве ВыполнитьПакет() содержит только служебную информацию — данные берут из запросов-выборок.

Консоль запросов 1С для отладки

Писать сложный текст сразу в модуле неудобно: проще собрать и проверить его отдельно. Для этого есть несколько инструментов:

  • Конструктор запроса в конфигураторе — вызывается из контекстного меню в модуле, строит текст визуально и правильно расставляет псевдонимы. Вариант «с обработкой результата» сразу генерирует код выполнения и обхода.
  • Консоль запросов — внешняя обработка, которую запускают через «Файл — Открыть» в режиме «1С:Предприятие». Фирма «1С» распространяет свой вариант для управляемых форм, есть и множество бесплатных аналогов. В консоли задают параметры, выполняют пакет и смотрят содержимое каждой временной таблицы.
  • Отладчик: при остановке на строке после Выполнить() выражение РезультатЗапроса.Выгрузить() в окне «Вычислить выражение» показывает данные таблицей. Для пакетов удобен метод ВыполнитьПакетСПромежуточнымиДанными(), который дополнительно возвращает содержимое временных таблиц.

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

Типичные ошибки при работе с запросами

  • Запрос в цикле. Выполнять запрос для каждой строки табличной части — главная причина медленного кода. Соберите ключи в массив или таблицу значений и сделайте один запрос с условием В или соединением с временной таблицей.
  • Склейка значений в текст. Подстановка даты или строки прямо в текст запроса ломается на кавычках и форматах и мешает кешированию плана. Используйте параметры.
  • Обращение через точку в выборке. Выборка.Документ.Контрагент.ИНН в цикле — это отдельное чтение объекта на каждой итерации. Получите нужное поле в самом запросе: Реализация.Контрагент.ИНН КАК ИНН.
  • Проверка пустоты через Выгрузить(). Если нужен только факт наличия данных, хватит Пустой() и ВЫБРАТЬ ПЕРВЫЕ 1.
  • Ожидание данных от Выполнить() в пакете. Метод вернёт результат только последнего запроса. Если последний — УНИЧТОЖИТЬ, результат будет пустым.
  • Обработка NULL. Поля из левого соединения при отсутствии парной записи возвращают NULL; оберните их в ЕСТЬNULL(Поле, 0), иначе арифметика в запросе даст NULL, а в коде — ошибку типов.

Когда что использовать

СпособЧто возвращаетКогда подходит
Выполнить().Выбрать()Выборку для последовательного обходаОднократная обработка строк, большие объёмы
Выполнить().Выгрузить()Таблицу значенийПоиск, сортировка, загрузка в табличную часть
Выполнить().Пустой()БулевоПроверка наличия данных
ВыполнитьПакет()Массив результатовНужны данные нескольких запросов пакета

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

Актуальность для новых версий

Объект Запрос и язык запросов работают одинаково во всех версиях платформы 8.3, включая 8.3.2x. Запросы выполняются только на сервере, поэтому в управляемых формах код размещают в процедурах с директивой &НаСервере или в серверных общих модулях. Метод ВыполнитьПакетСПромежуточнымиДанными() есть только в относительно новых версиях платформы — если его нет в синтакс-помощнике, используйте ВыполнитьПакет() и консоль запросов.

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

Чем отличается Выбрать() от Выгрузить()?

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

Как передать в запрос список значений?

Передайте массив или список значений через УстановитьПараметр() и используйте в тексте условие «Поле В (&Параметр)». Для иерархических справочников можно написать «В ИЕРАРХИИ (&Параметр)».

Почему Выполнить() для пакета возвращает пустой результат?

Выполнить() возвращает результат только последнего запроса пакета. Если последним стоит УНИЧТОЖИТЬ или ПОМЕСТИТЬ, используйте ВыполнитьПакет() и возьмите нужный элемент массива.

Где взять консоль запросов?

Консоль запросов — внешняя обработка: её открывают через «Файл — Открыть» в режиме 1С:Предприятие. Фирма «1С» распространяет свою версию для управляемых форм, существуют и бесплатные аналоги.

Дата в запросе 1С: ДАТАВРЕМЯ, период, МЕЖДУ, пустая и текущая дата

Запросы

Все приёмы работы с датами в языке запросов 1С: литералы и параметры, отбор за период, дата без времени, извлечение года и месяца, разность и сдвиг дат, пустая и текущая дата.

Запрос 1С, который генерирует даты по дням за период

Запросы

Готовый текст запроса 1С, который строит список всех дней между двумя датами без регистров и вспомогательных таблиц. Подходит для отчётов, где нужны строки по каждому дню.

Как получить в запросе начальные и конечные остатки на каждый день, месяц или другой период

Обычные формы, Управляемые формы, Запросы

Виртуальная таблица ОстаткиИОбороты не возвращает строки за дни без движений. Показываем запрос с таблицей дат, который даёт начальный и конечный остаток на каждый день периода.

XML в 1С программно: запись, чтение, DOM и выгрузка данных

Интеграция и веб-сервисы

Готовые примеры работы с XML на встроенном языке 1С: запись и чтение, DOM с XPath, преобразование XML в структуру, выгрузка справочника и разбор ошибок парсинга.