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С:Предприятие: конфигурация и база