1C-Code → Основы 1С → Запросы в работе: как писать, что ломается, чем ускорять
Методика письма, шесть типовых поломок и приёмы ускорения.
Глава 14 из 36 · чтение примерно 34 минут
Синтаксис запроса выучивается за вечер. Дальше начинается работа: человек смотрит на задачу «нужен отчёт по должникам» и не понимает, с какой строчки начать.
Эта глава про то, что идёт после синтаксиса: как превратить вопрос в запрос, какие шесть ошибок встречаются чаще всего и почему один и тот же ответ можно получить за одно обращение к базе, а можно за тысячу.
Запрос пишется не сверху вниз, а в другом порядке — от данных к результату. Пишут ВЫБРАТЬ первым, а думают о нём последним.
1. Назовите строку результата. Одна строка ответа — это что? Один товар? Один документ? Одна пара «товар + склад»? Пока не ответите, писать нечего: от этого зависит и группировка, и вид соединения.
2. Найдите главную таблицу. Ту, где строк ровно столько, сколько нужно в ответе. Нужны все товары, даже без продаж — главная таблица справочник, а продажи присоединяются. Нужны только проданные — главная таблица продажи.
3. Присоедините остальное. К главной таблице — ЛЕВОЕ СОЕДИНЕНИЕ, если строки главной терять нельзя, ВНУТРЕННЕЕ, если строка без пары не нужна.
4. Отберите и сгруппируйте. Условия на главную таблицу — в ГДЕ, условия на присоединённую — в ПО, условия на итог — в ИМЕЮЩИЕ. Это три разных места, и путать их нельзя.
5. И только теперь — ВЫБРАТЬ. Перечислите поля, дайте псевдонимы, задайте порядок.
// Задача: сколько документов у каждого клиента,
// включая тех, у кого их нет вовсе.
// 1. строка результата — один клиент
// 2. главная таблица — справочник: нужны все клиенты
ВЫБРАТЬ
Контрагенты.Наименование КАК Клиент,
КОЛИЧЕСТВО(Документы.Ссылка) КАК Документов
ИЗ
Справочник.Контрагенты КАК Контрагенты
ЛЕВОЕ СОЕДИНЕНИЕ Документ.РеализацияТоваров КАК Документы
ПО Документы.Контрагент = Контрагенты.Ссылка
СГРУППИРОВАТЬ ПО
Контрагенты.Наименование
УПОРЯДОЧИТЬ ПО
Документов УБЫВ,
Клиент
| Часть | Что означает |
|---|---|
КОЛИЧЕСТВО(Документы.Ссылка) | считаем поле присоединённой таблицы, а не (*): у клиента без документов там NULL, и он даст ноль |
ЛЕВОЕ СОЕДИНЕНИЕ | клиент без документов обязан остаться в ответе |
ПО Документы.Контрагент = Контрагенты.Ссылка | условие связи — в ПО; перенеси его в ГДЕ, и клиенты без документов исчезнут |
СГРУППИРОВАТЬ ПО | строка результата — клиент, значит группируем по клиенту |
КОЛИЧЕСТВО(*) и КОЛИЧЕСТВО(поле) — разные вещи
КОЛИЧЕСТВО(*) считает строки. После левого соединения у товара без продаж строка всё равно есть — одна, с NULL в правой части. Значит вместо нуля получится единица.
КОЛИЧЕСТВО(Поле) считает только непустые значения. NULL не считается, и выходит честный ноль.
Это первая ошибка, которую легко не заметить: ответ выглядит правдоподобно, просто у половины товаров лишняя единица.
Упражнение 1. Считаем то, чего нет
Выведите Клиент (наименование контрагента) и Документов — сколько документов реализации на него оформлено. Нужны все контрагенты; у тех, по кому документов нет, должен стоять 0. Порядок: по числу документов по убыванию, при равенстве — по клиенту.
Решается в тренажёре: код запускается и проверяется сразу.
Они не про синтаксис — запрос с любой из них выполняется и отдаёт правдоподобный ответ. В этом и беда.
| Поломка | Как проявляется | Лечение |
|---|---|---|
| Задвоение при соединении | Сумма документа сложилась столько раз, сколько в нём строк | Не суммировать поле шапки после соединения со строками: МАКСИМУМ или отдельный запрос |
| Условие в ГДЕ вместо ПО | Левое соединение молча стало внутренним, строки исчезли | Условие на присоединяемую таблицу — в секцию ПО |
| NULL вместо нуля | В отчёте пусто там, где ждали 0; арифметика с NULL даёт NULL | ЕСТЬNULL(Значение, 0) — и обязательно вокруг агрегата, а не внутри |
| Отбор по агрегату в ГДЕ | «Поле Сумма не найдено» или отбор сработал до суммирования | Условие на итог — в ИМЕЮЩИЕ |
| Забытый срез последних | Цена берётся любая из истории — какая попалась | РегистрСведений.Имя.СрезПоследних(&Дата) |
| Порядок не задан | Локально совпало, у заказчика порядок другой | Нужен порядок — пишите УПОРЯДОЧИТЬ ПО, без него он произволен |
ЕСТЬNULL снаружи агрегата, а не внутри
Это тонкость, на которой спотыкаются даже те, кто про NULL знает.
// не работает: складывать нечего, СУММА вернёт NULL СУММА(ЕСТЬNULL(Продажи.Сумма, 0)) // работает: подставляем ноль там, где итога не получилось ЕСТЬNULL(СУММА(Продажи.Сумма), 0)
Разница видна только у строк без единой пары: внутренний вариант подставляет ноль в то, чего нет, и всё равно остаётся ни с чем.
Упражнение 2. Первые три и ни одной лишней
Из среза последних РегистрСведений.ЦеныНоменклатуры выведите три самые дорогие розничные цены: колонки Товар и Цена. Порядок — по цене по убыванию, при равных ценах по товару.
Решается в тренажёре: код запускается и проверяется сразу.
Запрос в 1С почти всегда медленный по одной из трёх причин: он тащит лишнее, ходит в базу несколько раз или заставляет базу перебирать всё подряд там, где могла бы взять по индексу.
Не тащите лишние поля. ВЫБРАТЬ * в 1С не пишут вовсе. Каждое лишнее поле — это данные по сети, а ссылочное поле ещё и тянет представление.
Отбирайте до соединения, а не после. Условие в ГДЕ по главной таблице отсекает строки раньше, чем начнётся соединение.
Берите виртуальные таблицы, а не движения. Остатки(&Дата) и СрезПоследних(&Дата) платформа считает по служебным таблицам итогов — это на порядок дешевле, чем складывать движения самому.
Один пакет вместо трёх запросов. Несколько запросов через точку с запятой уходят в базу одним обращением, а промежуточный результат кладут в ПОМЕСТИТЬ и переиспользуют.
Никогда не запрашивайте в цикле. Это главный убийца производительности в 1С: сто строк — сто обращений. Список значений передают параметром и отбирают через В (&Список).
Отбор без вычислений по полю. ГДЕ ГОД(Док.Дата) = 2024 заставляет вычислить функцию для каждой строки; ГДЕ Док.Дата МЕЖДУ &Н И &К ложится на индекс.
✓
Для Каждого Имя Из Наименования Цикл
Запрос.УстановитьПараметр("Имя", Имя);
Выборка = Запрос.Выполнить().Выгрузить();
// … разбор одной строки
КонецЦикла;✕
Запрос.УстановитьПараметр("Список", Наименования);
// … ГДЕ Ном.Наименование В (&Список)
Выборка = Запрос.Выполнить().Выгрузить();Упражнение 3. Список вместо цикла
Параметр &Список уже содержит массив артикулов. Выведите Артикул и Группа тех позиций, чей артикул есть в списке. Порядок — по артикулу.
Решается в тренажёре: код запускается и проверяется сразу.
Когда промежуточный результат нужен дважды, его кладут во временную таблицу словом ПОМЕСТИТЬ. Запросы разделяются точкой с запятой и уходят в базу одним обращением; результатом считается последний запрос пакета.
ВЫБРАТЬ Док.Контрагент КАК Клиент, СУММА(Док.СуммаДокумента) КАК Выручка ПОМЕСТИТЬ ПоКлиентам ИЗ Документ.РеализацияТоваров КАК Док ГДЕ Док.Проведён СГРУППИРОВАТЬ ПО Док.Контрагент ; ВЫБРАТЬ ПоКлиентам.Клиент КАК Клиент ИЗ ПоКлиентам КАК ПоКлиентам
Временная таблица живёт до конца пакета. Считать одно и то же дважды не нужно, а читается такой запрос лучше вложенного.
Упражнение 4. Пакет с временной таблицей
Соберите выручку по контрагентам за проведённые документы во временную таблицу, а затем выберите из неё тех, у кого выручка больше 20 000. Колонки: Клиент и Выручка, порядок — по выручке по убыванию.
Решается в тренажёре: код запускается и проверяется сразу.
Чего запрос не умеет
Запрос только читает. Ни записать, ни изменить, ни удалить им нельзя — в языке нет ни UPDATE, ни DELETE, и это сделано нарочно: запись идёт через объекты и наборы записей, иначе обработчики модуля объекта не сработали бы.
Отсюда обычная связка: запросом получают данные, а меняют их в коде — по одному объекту, с проведением, проверками и всем, что к этому прилагается.
Выгрузить() отдаёт таблицу значений целиком — удобно, когда строк немного и по ним нужно ходить несколько раз.
Выбрать() отдаёт выборку, по которой идут Пока Выборка.Следующий() Цикл — так строки не грузятся в память все сразу. На тысячах строк разница заметна.
Пустой() отвечает на вопрос «нашлось ли что-нибудь» без выгрузки. Если ответ нужен только да/нет, это дешевле всего — особенно вместе с ВЫБРАТЬ ПЕРВЫЕ 1.
Пишите от строки результата, а не от ВЫБРАТЬ. Условие на присоединённую таблицу — в ПО, на итог — в ИМЕЮЩИЕ. Ноль вместо NULL ставьте снаружи агрегата. Порядок задавайте явно. В цикл запрос не ставьте никогда — передавайте список параметром.
Этого хватает, чтобы писать запросы, которые не придётся переписывать после первой же боевой базы.
Читать про код и писать код — разные умения. В тренажёре к этой главе идут упражнения: вы пишете решение, оно запускается на настоящем интерпретаторе 1С и проверяется сразу, без установки платформы.
← Язык запросов: читать данные пачками
→ Что такое 1С:Предприятие: конфигурация и база