Запросы в платформе «1С:Предприятие» — это мощный инструмент работы с данными, однако неверное их проектирование может привести к катастрофическому падению производительности всей системы, блокировкам таблиц базы данных и получению недостоверных результатов. В этой статье мы подробно разберем классическую задачу, которую часто предлагают кандидатам на собеседованиях или экзаменах «1С:Специалист».
На первый взгляд, запрос выглядит просто и логично: нам нужно получить список товаров из документа и сопоставить их с текущими остатками на складах. Давайте посмотрим на исходный текст этого запроса:
ВЫБРАТЬ
РеализацияТоваровУслугТовары.Склад,
РеализацияТоваровУслугТовары.Номенклатура,
РеализацияТоваровУслугТовары.ХарактеристикаНоменклатуры,
ТоварыНаСкладахОстатки.КоличествоОстаток
ИЗ
Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТоваровУслугТовары
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(&ДатаДокумента,) КАК ТоварыНаСкладахОстатки
ПО РеализацияТоваровУслугТовары.Номенклатура = ТоварыНаСкладахОстатки.Номенклатура
И РеализацияТоваровУслугТовары.ХарактеристикаНоменклатуры = ТоварыНаСкладахОстатки.ХарактеристикаНоменклатуры
И РеализацияТоваровУслугТовары.Склад = ТоварыНаСкладахОстатки.Склад
ГДЕ РеализацияТоваровУслуг.Ссылка = &Ссылка
Для опытного разработчика 1С этот запрос — настоящий сборник антипаттернов. В нем содержится как минимум пять серьезных ошибок разного уровня: от простых опечаток до фундаментальных архитектурных нарушений. Давайте подробно проанализируем каждую из них и выясним причины их возникновения.
Начнем с самой очевидной проблемы, из-за которой данный запрос вообще откажется выполняться и выдаст ошибку на этапе компиляции. Обратим внимание на секцию ГДЕ:
ГДЕ РеализацияТоваровУслуг.Ссылка = &Ссылка
В секции ИЗ мы выбираем данные из табличной части документа и присваиваем ей псевдоним (алиас) РеализацияТоваровУслугТовары. Физическая таблица самого документа Документ.РеализацияТоваровУслуг в запросе отсутствует. Платформа 1С просто «не поймет», откуда брать поле РеализацияТоваровУслуг.Ссылка.
Решение: Для исправления этой опечатки необходимо обращаться к реквизиту Ссылка через правильный псевдоним табличной части:
ГДЕ РеализацияТоваровУслугТовары.Ссылка = &Ссылка
Это самая критическая ошибка с точки зрения производительности СУБД. В запросе выполняется ЛЕВОЕ СОЕДИНЕНИЕ физической таблицы табличной части документа с виртуальной таблицей остатков РегистрНакопления.ТоварыНаСкладах.Остатки.
Выясним причину, почему это делать категорически запрещено стандартами разработки «1С»:
&ДатаДокумента, но не передаем никаких отборов по номенклатуре, складам или характеристикам.Index Scan или всей таблицы Table Scan вместо быстрого поиска Index Seek). На реальной многопользовательской базе это приведет к мгновенной блокировке таблиц и зависанию системы.Условие фильтрации ГДЕ РеализацияТоваровУслугТовары.Ссылка = &Ссылка применяется СУБД уже после того, как будут соединены гигантская таблица всех остатков и строки нашего документа. Это крайне неоптимально.
Решение: Согласно стандартам «1С», для решения этой проблемы необходимо использовать механизм Временных Таблиц (ВТ). Процесс оптимизации выглядит следующим образом:
Представим ситуацию: менеджер заполняет документ «Реализация товаров и услуг». По ошибке или в силу специфики бизнес-процесса он вносит один и тот же товар на один и тот же склад двумя разными строчками (например, в первой строке 5 штук товара «Смартфон», во второй строке — еще 3 штуки).
Посмотрим, что произойдет при выполнении нашего исходного запроса без группировки:
КоличествоОстаток будет выведено одно и то же число — например, общий остаток на складе, равный 6 штукам.КоличествоВСтроке > КоличествоОстаток, то проверка первой строки (5 < 6) пройдет успешно. Проверка второй строки (3 < 6) также пройдет успешно. Однако суммарно документ пытается списать 8 штук при наличии всего 6! Это неизбежно приведет к некорректному списанию товаров в «минус».Решение: Перед соединением с остатками данные табличной части документа обязательно должны быть сгруппированы (свернуты) по ключевым полям: Номенклатура, ХарактеристикаНоменклатуры, Склад, а количество должно быть просуммировано.
В секции ВЫБРАТЬ напрямую запрашивается поле остатка:
ТоварыНаСкладахОстатки.КоличествоОстаток
Поскольку мы используем ЛЕВОЕ СОЕДИНЕНИЕ, для товаров, по которым на складе вообще нет остатков (их количество равно нулю), база данных вернет значение NULL.
Если в коде модуля проведения мы попытаемся выполнить математические операции с этим полем или сравнить его с числом (например, проверить, хватает ли остатка), система выдаст ошибку времени выполнения (Runtime Error), так как операции типа Число - NULL или сравнение числа с NULL в языке 1С недопустимы.
Решение: Все поля, получаемые из правой (присоединяемой) таблицы левого соединения, обязательно должны быть обработаны встроенной функцией ЕСТЬNULL(). Например:
ЕСТЬNULL(ТоварыНаСкладахОстатки.КоличествоОстаток, 0) КАК КоличествоОстаток
В запросе используется параметр &ДатаДокумента:
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(&ДатаДокумента,)
Если данный запрос используется в механизме проведения документа для проверки остатков, то использование чистой даты документа — это грубая методологическая ошибка:
Решение: Для оперативного контроля остатков при проведении документов используется специальный объект платформы — Граница. Нам необходимо передать в параметры запроса момент времени с исключением движений самого документа:
Новый Граница(Источник.МоментВремени(), ВидГраницы.Исключая)
---
Давайте соберем все наши выводы воедино и напишем эталонный, оптимизированный и безопасный пакетный запрос, который полностью соответствует стандартам разработки «1С».
Шаг 1. Создаем временную таблицу товаров документа с группировкой
Сначала мы выбираем товары из документа по конкретной ссылке, группируем их, чтобы избежать дублирования строк, и помещаем результат во временную таблицу ВТ_ТоварыДокумента.
ВЫБРАТЬ
ТоварыТЧ.Склад КАК Склад,
ТоварыТЧ.Номенклатура КАК Номенклатура,
ТоварыТЧ.ХарактеристикаНоменклатуры КАК ХарактеристикаНоменклатуры,
СУММА(ТоварыТЧ.Количество) КАК КоличествоВДокументе
ПОМЕСТИТЬ ВТ_ТоварыДокумента
ИЗ
Документ.РеализацияТоваровУслуг.Товары КАК ТоварыТЧ
ГДЕ
ТоварыТЧ.Ссылка = &Ссылка
СГРУППИРОВАТЬ ПО
ТоварыТЧ.Склад,
ТоварыТЧ.Номенклатура,
ТоварыТЧ.ХарактеристикаНоменклатуры
ИНДЕКСИРОВАТЬ ПО
Номенклатура,
ХарактеристикаНоменклатуры,
Склад
;
Обратите внимание: Мы добавили инструкцию ИНДЕКСИРОВАТЬ ПО. Индексирование полей временной таблицы, по которым в дальнейшем будет происходить связь (соединение), существенно ускоряет работу СУБД.
Шаг 2. Соединяем временную таблицу с виртуальной таблицей остатков
Теперь мы выбираем данные из нашей подготовленной временной таблицы ВТ_ТоварыДокумента и левым соединением подключаем таблицу остатков. При этом мы обязательно передаем фильтры (нашу номенклатуру, характеристики и склады) прямо в параметры виртуальной таблицы остатков, чтобы СУБД выбирала только нужные строки.
ВЫБРАТЬ
ТоварыДокумента.Склад КАК Склад,
ТоварыДокумента.Номенклатура КАК Номенклатура,
ТоварыДокумента.ХарактеристикаНоменклатуры КАК ХарактеристикаНоменклатуры,
ТоварыДокумента.КоличествоВДокументе КАК КоличествоВДокументе,
ЕСТЬNULL(ТоварыНаСкладахОстатки.КоличествоОстаток, 0) КАК КоличествоОстаток
ИЗ
ВТ_ТоварыДокумента КАК ТоварыДокумента
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(
&Период,
(Номенклатура, Характеристика, Склад) В
(ВЫБРАТЬ
ТоварыИзВТ.Номенклатура,
ТоварыИзВТ.ХарактеристикаНоменклатуры,
ТоварыИзВТ.Склад
ИЗ ВТ_ТоварыДокумента КАК ТоварыИзВТ)
) КАК ТоварыНаСкладахОстатки
ПО ТоварыДокумента.Номенклатура = ТоварыНаСкладахОстатки.Номенклатура
И ТоварыДокумента.ХарактеристикаНоменклатуры = ТоварыНаСкладахОстатки.Характеристика
И ТоварыДокумента.Склад = ТоварыНаСкладахОстатки.Склад
Мы полностью переработали исходный некорректный запрос. Теперь он:
ЕСТЬNULL(), возвращая 0 вместо NULL.Именно такой подход к написанию запросов демонстрирует высокий профессиональный уровень разработчика и гарантирует стабильную работу информационной системы даже при очень больших объемах данных.