Условия, соединения и итоги: собираем запрос 1С от простого к сложному
Содержание
Запрос в 1С собирается из секций в строгом порядке: ВЫБРАТЬ → ИЗ → СОЕДИНЕНИЕ … ПО → ГДЕ → СГРУППИРОВАТЬ ПО → ИМЕЮЩИЕ → УПОРЯДОЧИТЬ ПО → ИТОГИ. Условия отбора пишут в ГДЕ через параметры &Имя, связи таблиц — через ЛЕВОЕ СОЕДИНЕНИЕ, суммы — через СУММА() с группировкой, а тип поля приводят функцией ВЫРАЗИТЬ.
Типичная ситуация: главбух просит «выгрузку продаж по контрагентам за квартал, только где сумма больше 100 тысяч». Отчёта такого нет, а язык запросов 1С решает задачу за десяток строк. Ниже разобраны все конструкции, которые для этого нужны, с примерами на объектах «Бухгалтерии предприятия 3.0». Имена справочников и регистров в вашей конфигурации могут отличаться — сверяйтесь с деревом метаданных в Конфигураторе.
Как устроен запрос в 1С: каркас и синтаксис
Язык запросов 1С похож на SQL, но работает не с таблицами СУБД, а с объектами конфигурации: Справочник.Номенклатура, Документ.РеализацияТоваровУслуг, РегистрСведений.КурсыВалют. Платформа сама переводит текст в SQL нужной СУБД — файловой, MS SQL или PostgreSQL.
Главное отличие от SQL: язык запросов 1С только читает данные. Команд UPDATE, DELETE, INSERT в нём нет — запись идёт через объекты встроенного языка.
Минимальный запрос
ВЫБРАТЬ
Номенклатура.Ссылка КАК Ссылка,
Номенклатура.Наименование КАК Наименование
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
НЕ Номенклатура.ПометкаУдаления
И НЕ Номенклатура.ЭтоГруппа
УПОРЯДОЧИТЬ ПО
Наименование
Что здесь есть:
- 🔹
КАКзадаёт псевдоним таблицы или поля. По псевдонимам потом обращаются к колонкам результата. - 🔹
Ссылка— уникальная ссылка на элемент. Её выбирают почти всегда, чтобы потом открыть объект. - 🔹 Ключевые слова пишутся в любом регистре. Работают и английские аналоги:
SELECT,FROM,WHERE.
Модификаторы после ВЫБРАТЬ
| Модификатор | Что делает | Пример |
|---|---|---|
РАЗЛИЧНЫЕ | Убирает дубли строк | ВЫБРАТЬ РАЗЛИЧНЫЕ Контрагент |
ПЕРВЫЕ N | Возвращает первые N строк | ВЫБРАТЬ ПЕРВЫЕ 10 … |
РАЗРЕШЕННЫЕ | Отбрасывает записи, закрытые ограничением прав (RLS) | ВЫБРАТЬ РАЗРЕШЕННЫЕ … |
Как выполнить запрос из кода
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка,
| Номенклатура.Наименование КАК Наименование
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Наименование ПОДОБНО &Шаблон";
Запрос.УстановитьПараметр("Шаблон", "%кабель%");
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Сообщить(Выборка.Наименование);
КонецЦикла;
Не набирайте текст вручную с нуля. В Конфигураторе в модуле вызовите контекстное меню → Конструктор запроса…: он сам расставит секции, псевдонимы и переносы |.
Условия в запросе 1С: ГДЕ, параметры и операторы
Условие отбора пишется в секции ГДЕ. Значения в текст не вклеивают, а передают параметром: &Дата, &Организация. Так запрос не ломается от кавычек в названиях и кэшируется СУБД.
Какими операторами выразить условие
- ⚖️ Сравнение:
=,<>,<,>,<=,>=. - 📅 Диапазон:
Дата МЕЖДУ &Начало И &Конец— обе границы включаются. - 📋 Список:
Склад В (&СписокСкладов)— параметром передают массив или список значений. - 🌳 Иерархия:
Номенклатура В ИЕРАРХИИ (&Группа)— группа и всё вложенное. - 🔤 Шаблон строки:
Наименование ПОДОБНО "%болт%".%— любое число символов,_— один символ. - ∅ Пустота:
Поле ЕСТЬ NULLиПоле ЕСТЬ НЕ NULL. - 📄 Тип ссылки:
Регистратор ССЫЛКА Документ.РеализацияТоваровУслуг. - 🔗 Логика:
И,ИЛИ,НЕи скобки.
Пример с периодом и предопределёнными значениями
ВЫБРАТЬ
Реализация.Ссылка КАК Документ,
Реализация.Контрагент КАК Контрагент,
Реализация.СуммаДокумента КАК Сумма
ИЗ
Документ.РеализацияТоваровУслуг КАК Реализация
ГДЕ
Реализация.Проведен
И Реализация.Дата МЕЖДУ &НачалоПериода И &КонецПериода
И Реализация.Контрагент <> ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка)
Поле Дата у документа хранит и время. Если передать в &КонецПериода дату 31.03 без времени, документы за 31 марта после 00:00:00 в выборку не попадут. Передавайте КонецДня(ДатаОкончания).
Вычисляемое условие: ВЫБОР КОГДА
Когда значение поля зависит от условия, используют конструкцию-аналог CASE:
ВЫБОР
КОГДА Реализация.СуммаДокумента >= 100000 ТОГДА "Крупная"
КОГДА Реализация.СуммаДокумента >= 10000 ТОГДА "Средняя"
ИНАЧЕ "Мелкая"
КОНЕЦ КАК КатегорияСделки
Как выразить соединение таблиц в запросе
Соединение нужно, когда данные лежат в разных таблицах: товары — в справочнике, остатки — в регистре, цены — в регистре сведений. Связь задаётся после слова ПО.
| Вид соединения | Что вернёт | Когда брать |
|---|---|---|
ВНУТРЕННЕЕ СОЕДИНЕНИЕ | Только строки, у которых есть пара в обеих таблицах | Нужны лишь совпадения |
ЛЕВОЕ СОЕДИНЕНИЕ | Все строки левой таблицы, справа — пара или NULL | «Все товары и их цена, если она есть» |
ПРАВОЕ СОЕДИНЕНИЕ | Зеркально левому | Редко; обычно таблицы меняют местами |
ПОЛНОЕ СОЕДИНЕНИЕ | Все строки обеих таблиц | Сверка двух источников |
Просто СОЕДИНЕНИЕ без уточнения означает внутреннее. Декартово произведение получают, перечислив таблицы в ИЗ через запятую.
Пример: все товары и их последняя цена
ВЫБРАТЬ
Номенклатура.Ссылка КАК Номенклатура,
ЕСТЬNULL(Цены.Цена, 0) КАК Цена
ИЗ
Справочник.Номенклатура КАК Номенклатура
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(
&Дата, ТипЦен = &ТипЦен) КАК Цены
ПО Номенклатура.Ссылка = Цены.Номенклатура
ГДЕ
НЕ Номенклатура.ЭтоГруппа
Здесь два приёма, без которых левое соединение работает неправильно.
- ЕСТЬNULL. У товаров без цены поле
Цены.ЦенаравноNULL. ФункцияЕСТЬNULL(Поле, 0)подставит ноль, и суммирование не сломается. - Где ставить отбор по правой таблице. Условие
ТипЦен = &ТипЦенстоит в параметрах виртуальной таблицы, а не вГДЕ.
Отбор по полю правой таблицы в секции ГДЕ превращает левое соединение во внутреннее: строки с NULL справа не проходят условие и пропадают. Такое условие пишут в ПО или в параметрах виртуальной таблицы.
Неявное соединение через точку
Запись Реализация.Контрагент.ИНН — это тоже соединение: платформа сама подтянет таблицу контрагентов. Удобно, но у составных полей (например, Регистратор в регистре) такая точка соединяет сразу со всеми возможными типами документов. Об этом — в разделе об оптимизации.
Группировка и агрегатные функции в языке запросов
Агрегатные функции сворачивают много строк в одну:
- ➕
СУММА(Поле)— сумма; - 🔢
КОЛИЧЕСТВО(Поле)иКОЛИЧЕСТВО(РАЗЛИЧНЫЕ Поле)— число строк или уникальных значений; - 📈
МАКСИМУМ,МИНИМУМ,СРЕДНЕЕ.
Правило одно: каждое поле в ВЫБРАТЬ без агрегатной функции обязано стоять в СГРУППИРОВАТЬ ПО. Иначе запрос не выполнится с ошибкой Поле не входит в группу.
Задача главбуха: продажи по контрагентам больше 100 тысяч
ВЫБРАТЬ
Товары.Ссылка.Контрагент КАК Контрагент,
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Товары.Ссылка) КАК ЧислоДокументов,
СУММА(Товары.Сумма) КАК Выручка
ИЗ
Документ.РеализацияТоваровУслуг.Товары КАК Товары
ГДЕ
Товары.Ссылка.Проведен
И Товары.Ссылка.Дата МЕЖДУ &НачалоПериода И &КонецПериода
СГРУППИРОВАТЬ ПО
Товары.Ссылка.Контрагент
ИМЕЮЩИЕ
СУММА(Товары.Сумма) > 100000
УПОРЯДОЧИТЬ ПО
Выручка УБЫВ
Разница между ГДЕ и ИМЕЮЩИЕ:
| Секция | Когда срабатывает | Что можно проверять |
|---|---|---|
ГДЕ | До группировки, по каждой строке | Обычные поля |
ИМЕЮЩИЕ | После группировки | Результаты агрегатных функций |
Группировка по месяцам
Чтобы свернуть продажи по месяцам, дату приводят к началу периода:
НАЧАЛОПЕРИОДА(Товары.Ссылка.Дата, МЕСЯЦ) КАК Месяц
Это же выражение повторяют в СГРУППИРОВАТЬ ПО. Аналогично работают ГОД(), КВАРТАЛ(), ДЕНЬНЕДЕЛИ(), а для разницы дат — РАЗНОСТЬДАТ(Дата1, Дата2, ДЕНЬ).
Итоги для отчёта
Секция ИТОГИ СУММА(Выручка) ПО ОБЩИЕ, Контрагент добавляет к результату строки промежуточных и общих итогов. Обходят такой результат выборкой с параметром ОбходРезультатаЗапроса.ПоГруппировкам.
Вложенные запросы, временные таблицы и пакеты
Вложенный запрос
Подзапрос в условии — самый короткий способ отобрать данные по другой таблице:
ГДЕ
Контрагенты.Ссылка В (
ВЫБРАТЬ РАЗЛИЧНЫЕ Реализация.Контрагент
ИЗ Документ.РеализацияТоваровУслуг КАК Реализация
ГДЕ Реализация.Дата >= &НачалоПериода)
Вложенный запрос в ИЗ или в соединении тоже допустим, но СУБД плохо оценивает его размер. На больших базах это частая причина медленных отчётов.
Временные таблицы и пакетный запрос
Надёжнее разбить задачу на шаги: результат первого шага положить во временную таблицу, а потом соединяться с ней. Запросы пакета разделяют точкой с запятой.
ВЫБРАТЬ
Реализация.Контрагент КАК Контрагент,
СУММА(Реализация.СуммаДокумента) КАК Выручка
ПОМЕСТИТЬ ВТ_Продажи
ИЗ
Документ.РеализацияТоваровУслуг КАК Реализация
ГДЕ
Реализация.Проведен
И Реализация.Дата МЕЖДУ &НачалоПериода И &КонецПериода
СГРУППИРОВАТЬ ПО
Реализация.Контрагент
ИНДЕКСИРОВАТЬ ПО
Контрагент
;
////////////////////////////////////////////////////////////
ВЫБРАТЬ
Контрагенты.Ссылка КАК Контрагент,
Контрагенты.ИНН КАК ИНН,
ЕСТЬNULL(ВТ_Продажи.Выручка, 0) КАК Выручка
ИЗ
Справочник.Контрагенты КАК Контрагенты
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Продажи КАК ВТ_Продажи
ПО Контрагенты.Ссылка = ВТ_Продажи.Контрагент
ГДЕ
НЕ Контрагенты.ЭтоГруппа
Как это работает:
ПОМЕСТИТЬставят сразу после списка полей, доИЗ.ИНДЕКСИРОВАТЬ ПОстроит индекс по полю, по которому потом будет соединение. На малых таблицах он не нужен, на десятках тысяч строк — ускоряет.Запрос.Выполнить()вернёт результат последнего запроса пакета,Запрос.ВыполнитьПакет()— массив результатов всех запросов.- Чтобы временная таблица жила между разными запросами, создайте
Новый МенеджерВременныхТаблици присвойте его свойствуЗапрос.МенеджерВременныхТаблиц.
Ненужную временную таблицу удаляют командой УНИЧТОЖИТЬ ВТ_Продажи отдельным запросом пакета. Это освобождает память, когда пакет длинный.
Объединение результатов
ОБЪЕДИНИТЬ ВСЕ склеивает результаты двух запросов в одну таблицу. Число и порядок полей должны совпадать, псевдонимы берутся из первого запроса.
Функция ВЫРАЗИТЬ и быстрые запросы: индексы, план, ошибки
Как выразить тип поля
Функция ВЫРАЗИТЬ приводит значение к нужному типу. Без неё не обойтись в трёх случаях.
| Задача | Запись |
|---|---|
Сравнить или сгруппировать строку неограниченной длины (например, Комментарий) | ВЫРАЗИТЬ(Док.Комментарий КАК СТРОКА(200)) |
| Взять реквизит у составного поля, зная тип | ВЫРАЗИТЬ(Остатки.Регистратор КАК Документ.ПоступлениеТоваровУслуг).Контрагент |
| Задать разрядность числа | ВЫРАЗИТЬ(Сумма / Количество КАК ЧИСЛО(15, 2)) |
Второй случай — главный приём ускорения. Запись Регистратор.Контрагент заставляет платформу соединиться со всеми таблицами документов, которые пишут в регистр. ВЫРАЗИТЬ оставляет одно соединение.
Что тормозит запросы
- 🐢 Запрос в цикле. Сотня запросов по одной строке медленнее одного запроса на сто строк. Собирайте ссылки в массив и передавайте через
В (&Массив). - 🐢 Отбор виртуальной таблицы в ГДЕ. Условие по измерению пишите в параметрах:
Остатки(&Период, Склад = &Склад). Тогда платформа отберёт данные до расчёта остатков, а не после. - 🐢 ИЛИ по разным полям. Условие
А = &X ИЛИ Б = &Yмешает СУБД использовать индекс. Часто быстрее два запроса черезОБЪЕДИНИТЬ ВСЕ. - 🐢 Соединение с подзапросом. Замените временной таблицей с индексом.
- 🐢 Лишняя точка. Каждая
Ссылка.Реквизит.Реквизит— новое соединение. Выбирайте только то, что выводите. - 🐢 РАЗЛИЧНЫЕ «на всякий случай». Это сортировка всего результата. Ставьте только там, где дубли реально возможны.
Как посмотреть, что делает СУБД
Индексы в 1С создаются не командой SQL, а свойствами метаданных. Измерения регистров индексируются автоматически, у реквизитов есть свойство Индексировать. Добавлять индекс без замера смысла нет.
Порядок замера такой:
- В Конфигураторе включите
Отладка → Замер производительностии выполните код с запросом — увидите время по строкам. - Для плана выполнения настройте технологический журнал: в файле
logcfg.xmlсобытиеDBMSSQL(илиDBPOSTGRSдля PostgreSQL) и элемент<plansql/>. - На MS SQL план также показывает SQL Server Profiler, на PostgreSQL —
EXPLAIN ANALYZEпо тексту запроса из журнала.
Технологический журнал на рабочем сервере быстро растёт. Включайте его коротким окном и с фильтром по базе, потом выключайте.
Примеры запросов 1С под частые задачи
Остатки по счёту на дату
Для «Бухгалтерии предприятия» остатки берут из регистра бухгалтерии Хозрасчетный:
ВЫБРАТЬ
Остатки.Субконто1 КАК Номенклатура,
Остатки.КоличествоОстаток КАК Количество,
Остатки.СуммаОстаток КАК Сумма
ИЗ
РегистрБухгалтерии.Хозрасчетный.Остатки(
&Дата,
Счет В ИЕРАРХИИ (&Счет41),
,
Организация = &Организация) КАК Остатки
Параметр &Счет41 — ссылка на счёт 41 из плана счетов Хозрасчетный: с В ИЕРАРХИИ попадут и все его субсчета. Учтите: остатки на дату берутся на начало этого момента, поэтому для конца дня передают Новый Граница(КонецДня(Дата), ВидГраницы.Включая).
Дубли в справочнике
ВЫБРАТЬ
Контрагенты.ИНН КАК ИНН,
КОЛИЧЕСТВО(Контрагенты.Ссылка) КАК Сколько
ИЗ
Справочник.Контрагенты КАК Контрагенты
ГДЕ
Контрагенты.ИНН <> ""
И НЕ Контрагенты.ПометкаУдаления
СГРУППИРОВАТЬ ПО
Контрагенты.ИНН
ИМЕЮЩИЕ
КОЛИЧЕСТВО(Контрагенты.Ссылка) > 1
Документы без проводок
Проведённый документ, по которому нет записей в регистре, ищут левым соединением с проверкой на NULL:
ВЫБРАТЬ
Реализация.Ссылка КАК Документ
ИЗ
Документ.РеализацияТоваровУслуг КАК Реализация
ЛЕВОЕ СОЕДИНЕНИЕ РегистрБухгалтерии.Хозрасчетный КАК Проводки
ПО Реализация.Ссылка = Проводки.Регистратор
ГДЕ
Реализация.Проведен
И Проводки.Регистратор ЕСТЬ NULL
Здесь проверка правой таблицы в ГДЕ стоит намеренно: нам нужны как раз строки без пары.
Отладка запроса без программирования
Готовый текст удобно проверять во внешней обработке «Консоль запросов»: вставляете запрос, задаёте параметры, смотрите результат таблицей. Запускается она в режиме 1С:Предприятие через Файл → Открыть. Права на открытие внешних обработок у пользователя должны быть.
Частые вопросы
Чем ОБЪЕДИНИТЬ отличается от ОБЪЕДИНИТЬ ВСЕ?
ОБЪЕДИНИТЬ удаляет одинаковые строки, ОБЪЕДИНИТЬ ВСЕ оставляет все. Второй вариант быстрее — используйте его, если дублей быть не может.Можно ли изменить данные запросом 1С?
Как убрать время из даты в результате запроса?
НАЧАЛОПЕРИОДА(Поле, ДЕНЬ). Значение останется датой, но со временем 00:00:00, и по нему можно группировать.Почему СУММА вернула пусто, а не ноль?
NULL, агрегатная функция тоже даёт NULL. Оберните её: ЕСТЬNULL(СУММА(Поле), 0).Работает ли в запросе 1С синтаксис SQL, например TOP или LIMIT?
SELECT, WHERE), но это синтаксис самого языка запросов 1С. Вместо TOP/LIMIT пишут ПЕРВЫЕ N (TOP N) сразу после ВЫБРАТЬ.