1C-CodeОсновы 1С → Язык запросов: читать данные пачками

Язык запросов: читать данные пачками

Как получать данные из базы одним обращением вместо перебора.

Глава 13 из 36 · чтение примерно 28 минут

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

Разница не в стиле, а в количестве обращений к базе. Перебор тысячи товаров означает тысячу обращений, запрос — одно. На этом и строится вся оптимизация в 1С.

Простейший запрос

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

Выборка = Запрос.Выполнить().Выгрузить();
ЧастьЧто означает
ВЫБРАТЬкакие поля нужны в результате
КАК Товарпсевдоним: имя колонки в результате
ИЗиз какой таблицы берём данные
КАК Товкороткое имя таблицы для ссылок на её поля
ГДЕусловие отбора строк
УПОРЯДОЧИТЬ ПОсортировка результата
|продолжение строки: текст запроса длинный, его переносят
Выполнить()отправить запрос в базу
Выгрузить()получить результат таблицей значений

Порядок частей запроса фиксирован

Части пишутся строго в этом порядке:

ВЫБРАТЬИЗ → соединения → ГДЕСГРУППИРОВАТЬ ПОИМЕЮЩИЕУПОРЯДОЧИТЬ ПОИТОГИ

Поменять местами нельзя — запрос не разберётся. Зато порядок легко запомнить: сначала что берём, потом откуда, потом какие строки, потом как группируем, в конце как сортируем.

Упражнение 1. Первый запрос

Напишите запрос, который выберет Наименование и Артикул из справочника Справочник.Номенклатура, но только услуги — у них реквизит Услуга равен Истине. Должны получиться две строки.

Решается в тренажёре: код запускается и проверяется сразу.

Параметры

Значения не вставляют в текст запроса — их передают параметрами через &:

Запрос.Текст = "… ГДЕ Номенклатура.ГруппаТоваров = &Группа";
Запрос.УстановитьПараметр("Группа", "Метизы");

Так безопаснее и быстрее: база запоминает разобранный запрос и переиспользует его с разными значениями.

Упражнение 2. Отбор параметром

Выберите Номер и СуммаДокумента из Документ.РеализацияТоваров — только проведённые и только начиная с даты в параметре &НачалоПериода. Параметр уже установлен на 01.06.2024; ожидаются три строки.

Решается в тренажёре: код запускается и проверяется сразу.

Сколько строк брать: ПЕРВЫЕ и УПОРЯДОЧИТЬ ПО

Две части запроса, которые почти всегда ходят парой, и именно поэтому их путают.

УПОРЯДОЧИТЬ ПО ставят в самом конце. Оно сортирует то, что получилось: УПОРЯДОЧИТЬ ПО Дата УБЫВ — сначала свежие. Без него порядок строк не определён: база вернёт их так, как ей удобнее, и сегодня это одно, а завтра другое.

ВЫБРАТЬ ПЕРВЫЕ 10 оставляет десять строк. Слово «первые» здесь означает «первые в том порядке, который вы задали». Никакого своего порядка у него нет.

ПЕРВЫЕ без УПОРЯДОЧИТЬ — это случайные строки

«Возьми десять последних документов» пишут как ВЫБРАТЬ ПЕРВЫЕ 10 … ИЗ Документ.Реализация — и получают десять любых.

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

Правило: увидели ПЕРВЫЕ — ищите глазами УПОРЯДОЧИТЬ ПО. Нет его — это ошибка, даже если сегодня всё сходится.

РАЗЛИЧНЫЕ: откуда берутся дубли

ВЫБРАТЬ РАЗЛИЧНЫЕ убирает полностью одинаковые строки результата. Выглядит как удобная мелочь, а на деле это чаще всего признак, что в запросе что-то не так.

Настоящая причина дублей почти всегда одна — соединение, которое размножило строки. Спрятать их под РАЗЛИЧНЫЕ можно, но если рядом есть СУММА, она уже сложила лишнее, и итог завышен независимо от РАЗЛИЧНЫЕ.

Поэтому порядок такой: сначала понять, откуда взялась вторая строка, и только потом решать, нужны ли РАЗЛИЧНЫЕ. Законный случай тоже бывает — «список контрагентов, у которых были продажи»: там дубли по смыслу, и убрать их правильно.

Соединения

Данные обычно лежат в разных таблицах: товары в одной, продажи в другой. Соединение связывает их по общему полю.

Три вида соединений

ВНУТРЕННЕЕ — только те строки, где пара нашлась

Молоток выпал — продаж по нему нет

ЛЕВОЕ — все строки слева, справа NULL если пары нет

Молоток остался, у него будет NULL

Условие в ГДЕ ломает левое соединение

Это самая частая ошибка при работе с соединениями.

// левое соединение превратилось во внутреннее
ЛЕВОЕ СОЕДИНЕНИЕ Продажи КАК Продажи
	ПО Продажи.Товар = Номенклатура.Ссылка
ГДЕ Продажи.Сумма > 1000        // ← товары без продаж исчезнут

// правильно: условие к присоединяемой таблице — в ПО
ЛЕВОЕ СОЕДИНЕНИЕ Продажи КАК Продажи
	ПО Продажи.Товар = Номенклатура.Ссылка
		И Продажи.Сумма > 1000

Причина: ГДЕ выполняется после соединения. У строки без пары все поля правой таблицы равны NULL, а сравнение с NULL никогда не истинно — строка отбрасывается.

Отсюда правило: условия к левой таблице ставят в ГДЕ, к присоединяемой — в ПО.

Группировка и агрегаты

Когда нужны итоги, а не отдельные строки, применяют группировку. Все поля, не попавшие под агрегатную функцию, обязаны быть в СГРУППИРОВАТЬ ПО.

ВЫБРАТЬ
	Продажи.Товар КАК Товар,
	СУММА(Продажи.Сумма) КАК Выручка,
	КОЛИЧЕСТВО(*) КАК Продаж
ИЗ
	Продажи КАК Продажи
СГРУППИРОВАТЬ ПО
	Продажи.Товар

Агрегаты: СУММА, КОЛИЧЕСТВО, МАКСИМУМ, МИНИМУМ, СРЕДНЕЕ. Все они игнорируют NULL — это иногда используют намеренно.

Упражнение 3. Оборот по контрагентам

Посчитайте оборот по каждому контрагенту: Контрагент и сумму по СуммаДокумента с именем Оборот. Из Документ.РеализацияТоваров.

Решается в тренажёре: код запускается и проверяется сразу.

ГДЕ против ИМЕЮЩИЕ

Оба отбирают строки, но в разные моменты.

ГДЕ работает до группировки и агрегатов не видит. Написать ГДЕ СУММА(...) > 100 нельзя — это ошибка.

ИМЕЮЩИЕ работает после и как раз для агрегатов:

ГДЕ Продажи.Дата >= &Начало        // отбор строк до группировки
СГРУППИРОВАТЬ ПО Продажи.Товар
ИМЕЮЩИЕ СУММА(Продажи.Сумма) > 1000  // отбор групп после

Практическое следствие: отбор по дате — в ГДЕ, он уменьшает объём до группировки. Отбор по итогу — только в ИМЕЮЩИЕ.

ВЫБОР: условие прямо в запросе

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

Делать это в цикле после запроса можно, но незачем: у языка запросов есть ВЫБОР. Он читается вслух почти как предложение — «выбор: когда так, тогда это, иначе то, конец».

Выбор внутри запроса

ВЫБРАТЬ
	Номер,
	ВЫБОР
		КОГДА СуммаДокумента > 100000 ТОГДА "крупный"
		КОГДА СуммаДокумента > 50000 ТОГДА "средний"
		ИНАЧЕ "обычный"
	КОНЕЦ КАК Размер
ИЗ
	Документ.РеализацияТоваров
ЧастьЧто означает
КОГДА … ТОГДАусловий может быть несколько; проверяются по порядку, срабатывает ПЕРВОЕ подошедшее
порядок важенпоставьте «больше 50000» выше «больше 100000» — и крупных не станет: они попадут в средние
ИНАЧЕесли его нет и ни одно условие не подошло, получится NULL — а не пустая строка
КОНЕЦобязателен: без него запрос не соберётся
все ветки — одного типавернуть из одной ветки строку, а из другой число нельзя

ОБЪЕДИНИТЬ и ОБЪЕДИНИТЬ ВСЕ

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

Требование к частям одно: одинаковое число колонок и совместимые типы. Имена колонок берутся из первой части — псевдонимы в остальных ни на что не влияют.

И вот различие, на котором ловят: ОБЪЕДИНИТЬ убирает одинаковые строки, ОБЪЕДИНИТЬ ВСЕ — оставляет все. Разница в цене тоже есть: чтобы убрать дубли, базе надо отсортировать весь результат.

Поэтому по умолчанию пишут ОБЪЕДИНИТЬ ВСЕ, а простое ОБЪЕДИНИТЬ — только когда дубли действительно надо убрать. Иначе две одинаковые по сумме продажи схлопнутся в одну, и отчёт недосчитается денег.

Упражнение 4. Отбор по итогу

Тот же оборот по контрагентам, но оставить только тех, у кого он больше 20000. Отбирать надо по итогу, а не по отдельным документам.

Решается в тренажёре: код запускается и проверяется сразу.

Виртуальные таблицы

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

Основные виртуальные таблицы

ТаблицаЧто даётПример
Остаткисостояние на моментРегистрНакопления.ОстаткиТоваров.Остатки
Оборотыприход и расход за периодРегистрНакопления.ОстаткиТоваров.Обороты
СрезПоследнихактуальная запись на датуРегистрСведений.Цены.СрезПоследних(&Дата)
СрезПервыхсамая ранняя записьРегистрСведений.Цены.СрезПервых

Составные типы и ВЫРАЗИТЬ

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

Через точку у такого поля доступны только те реквизиты, которые есть у всех возможных видов. Поэтому Регистратор.Контрагент обычно не работает: у части документов контрагента нет.

Решение — ВЫРАЗИТЬ: сказать запросу, какой именно вид вас интересует.

ВЫРАЗИТЬ(Регистратор КАК Документ.РеализацияТоваров).Контрагент — теперь поле доступно. У записей с другими документами там окажется NULL, и это правильно: у них такого реквизита и правда нет.

Второе применение — производительность. Обращение через точку к полю составного типа заставляет базу соединиться со всеми таблицами, которые туда помещаются. ВЫРАЗИТЬ оставляет одну.

Функции запроса, без которых не обойтись

ФункцияЧто делаетТипичное применение
ЕСТЬNULL(П, 0)подменяет NULL значениемпосле любого левого соединения
ВЫРАЗИТЬ(П КАК Тип)приводит составной тип к одному видуРегистратор, ссылка с несколькими типами
ПОДСТРОКА(С, 1, 10)кусок строкиобрезать длинный комментарий для отчёта
НАЧАЛОПЕРИОДА(Д, МЕСЯЦ)первое мгновение периодагруппировка по месяцам
КОНЕЦПЕРИОДА(Д, ДЕНЬ)последнее мгновение периодаграница отбора «включая весь день»
ДОБАВИТЬКДАТЕ(Д, ДЕНЬ, 30)сдвиг датысрок оплаты, просрочка
РАЗНОСТЬДАТ(Д1, Д2, ДЕНЬ)сколько между датамидни просрочки, стаж
ЗНАЧЕНИЕ(Перечисление.Х.Y)значение из конфигурации прямо в текстеотбор по перечислению без параметра

Точка в запросе и точка в коде

Выглядят одинаково, стоят по-разному.

В коде каждое обращение через точку дочитывает объект из базы. В цикле по тысяче строк это тысяча обращений.

В запросе точка разворачивается в соединение — одно на весь запрос, сколько бы строк ни было.

// в запросе — дёшево
ВЫБРАТЬ Продажи.Товар.Артикул КАК Артикул

// в коде — обращение на каждой итерации
Для Каждого СтрокаФайла Из Строки Цикл
	Артикул = СтрокаФайла.Товар.Артикул;   // ← дорого
КонецЦикла;

Правило: всё, что понадобится, запрашивайте сразу в запросе, а не дочитывайте потом в цикле.

Упражнение 5. Точка вместо соединения

Выберите Номер документа и регион его контрагента под именем Регион — только по контрагентам из Новосибирска. Соединение писать не нужно: хватит точки.

Решается в тренажёре: код запускается и проверяется сразу.

Упражнение 6. Три самых крупных

Из Документ.РеализацияТоваров выберите три документа с наибольшей суммой: Номер, СуммаДокумента и колонку Размер — «крупный», если сумма больше 50000, иначе «обычный». Самые крупные должны идти первыми.

Решается в тренажёре: код запускается и проверяется сразу.

Что делать с результатом

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

// таблицей значений — когда нужны все строки сразу
Таблица = Результат.Выгрузить();

// выборкой — когда идём по строкам один раз
Выборка = Результат.Выбрать();
Пока Выборка.Следующий() Цикл
	Сообщить(Выборка.Товар);
КонецЦикла;

// пустой ли результат
Если Результат.Пустой() Тогда

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

Как закрепить

Читать про код и писать код — разные умения. В тренажёре к этой главе идут упражнения: вы пишете решение, оно запускается на настоящем интерпретаторе 1С и проверяется сразу, без установки платформы.

Открыть эту главу в тренажёре

Формы: клиент, сервер и управление элементами
Запросы в работе: как писать, что ломается, чем ускорять