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С и проверяется сразу, без установки платформы.
← Формы: клиент, сервер и управление элементами
→ Запросы в работе: как писать, что ломается, чем ускорять