Написание запросов к базе данных — ключевой навык разработчика в среде 1С:Предприятие. На начальном этапе простые выборки из одного справочника или документа не вызывают сложностей. Однако при переходе к пакетам запросов, сложным соединениям таблиц и расчетам остатков разработчики часто сталкиваются с непониманием логики работы СУБД и ошибками производительности. Разберем по шагам, как выстроить системное понимание языка запросов 1С и перейти от простых выборок к оптимизированным пакетным алгоритмам.
Для правильного построения запросов необходимо четко понимать математическую природу базовых операций реляционной алгебры. Рассмотрим две ключевые операции, которые начинающие разработчики нередко путают:
СОЕДИНЕНИЕ / JOIN): связывают строки двух таблиц по горизонтали на основании заданного условия связи (ПО / ON). При внутреннем соединении (ВНУТРЕННЕЕ СОЕДИНЕНИЕ) в результат попадают только те строки, для которых нашлось соответствие в обеих таблицах. При левом внешнем соединении (ЛЕВОЕ СОЕДИНЕНИЕ) выбираются все строки из левой таблицы, а из правой подтягиваются совпадающие данные; если соответствия нет, поля правой таблицы заполняются значением NULL.ОБЪЕДИНИТЬ / UNION): объединяют результаты двух независимых выборок по вертикали. Наборы полей в объединяемых запросах должны строго совпадать по количеству и типам данных. Оператор ОБЪЕДИНИТЬ по умолчанию удаляет дубликаты строк (аналог UNION DISTINCT в SQL), в то время как ОБЪЕДИНИТЬ ВСЕ сохраняет все строки, работая значительно быстрее за счет отсутствия дополнительной сортировки.Проанализируем частую ошибку при отборе агрегированных данных. В языке запросов 1С предусмотрено два оператора фильтрации с принципиально разным порядком выполнения:
ГДЕ (WHERE): фильтрует исходные записи до того, как к ним будут применены группировки и агрегатные функции (СУММА, КОЛИЧЕСТВО, МИНИМУМ, МАКСИМУМ, СРЕДНЕЕ).ИМЕЮЩИЕ (HAVING): накладывает условие на уже сгруппированные данные и вычисленные агрегаты. В конструкторе запросов 1С это условие формируется на закладке «Условия», если в левой части выражения указана агрегатная функция.Посмотрим на пример правильного использования обоих операторов в одном запросе:
ВЫБРАТЬ
ПродажиОбороты.Номенклатура КАК Номенклатура,
СУММА(ПродажиОбороты.КоличествоОборот) КАК ВсегоПродано
ИЗ
РегистрНакопления.Продажи.Обороты(&НачалоПериода, &КонецПериода, , ) КАК ПродажиОбороты
ГДЕ
ПродажиОбороты.Номенклатура.Услуга = ЛОЖЬ
СГРУППИРОВАТЬ ПО
ПродажиОбороты.Номенклатура
ИМЕЮЩИЕ
СУММА(ПродажиОбороты.КоличествоОборот) > 100
Выясним причину падения производительности при работе с регистрами. Платформа 1С предоставляет готовые виртуальные таблицы (Остатки, Обороты, СрезПервых, СрезПоследних). Главное правило при их использовании — передавать параметры отбора непосредственно в круглые скобки виртуальной таблицы, а не выносить их в блок ГДЕ.
Если условие помещено в блок ГДЕ, СУБД сначала рассчитывает остатки или обороты по всей базе данных за всю историю и лишь затем отсекает ненужные записи. При передаче параметров в параметры виртуальной таблицы расчет выполняется только по отфильтрованному срезу, что снижает нагрузку на сервер в десятки раз.
Когда требуется соединить множество таблиц, рассчитать промежуточные показатели и получить сложный отчет, единый многоуровневый запрос становится нечитаемым и неоптимальным. Рассмотрим методику пошаговой декомпозиции («метод конвейера») с использованием временных таблиц (директива ПОМЕСТИТЬ):
ИНДЕКСИРОВАТЬ ПО. Это исключает полное сканирование (Table Scan) при последующих соединениях.УНИЧТОЖИТЬ для освобождения памяти СУБД.Разберем пример эффективного пакетного запроса:
ВЫБРАТЬ
ЗаказКлиентаТовары.Номенклатура КАК Номенклатура,
СУММА(ЗаказКлиентаТовары.Количество) КАК Заказано
ПОМЕСТИТЬ ВТ_ТоварыЗаказа
ИЗ
Документ.ЗаказКлиента.Товары КАК ЗаказКлиентаТовары
ГДЕ
ЗаказКлиентаТовары.Ссылка = &СсылкаНаЗаказ
СГРУППИРОВАТЬ ПО
ЗаказКлиентаТовары.Номенклатура
ИНДЕКСИРОВАТЬ ПО
Номенклатура
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ_ТоварыЗаказа.Номенклатура КАК Номенклатура,
ВТ_ТоварыЗаказа.Заказано КАК Заказано,
ЕСТЬNULL(ТоварыНаСкладахОстатки.ВНаличииОстаток, 0) КАК ОстатокНаСкладе
ИЗ
ВТ_ТоварыЗаказа КАК ВТ_ТоварыЗаказа
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(
&Период,
Номенклатура В
(ВЫБРАТЬ
ВТ.Номенклатура
ИЗ
ВТ_ТоварыЗаказа КАК ВТ)) КАК ТоварыНаСкладахОстатки
ПО ВТ_ТоварыЗаказа.Номенклатура = ТоварыНаСкладахОстатки.Номенклатура
;
////////////////////////////////////////////////////////////////////////////////
УНИЧТОЖИТЬ ВТ_ТоварыЗаказа
Для устойчивого освоения навыков разработки запросов рекомендуем придерживаться следующего плана:
SELECT, WHERE, GROUP BY, HAVING, все виды JOIN) дает фундаментальное понимание того, во что транслируются запросы платформы 1С на уровне СУБД.Документ.Контрагент.ОсновнойДоговор.Валюта), так как это порождает скрытые неявные соединения. Для полей составного типа всегда применяйте функцию ВЫРАЗИТЬ(Поле КАК Тип).