Как найти и исправить глобальные ошибки в запросе соединения документа с виртуальной таблицей остатков в 1С?

Программист 1С v8.3 (Управляемые формы) Управленческий учет IT и автоматизация бизнеса
← На главную

Запросы в платформе «1С:Предприятие» — это мощный инструмент работы с данными, однако неверное их проектирование может привести к катастрофическому падению производительности всей системы, блокировкам таблиц базы данных и получению недостоверных результатов. В этой статье мы подробно разберем классическую задачу, которую часто предлагают кандидатам на собеседованиях или экзаменах «1С:Специалист».

На первый взгляд, запрос выглядит просто и логично: нам нужно получить список товаров из документа и сопоставить их с текущими остатками на складах. Давайте посмотрим на исходный текст этого запроса:


ВЫБРАТЬ
   РеализацияТоваровУслугТовары.Склад,
   РеализацияТоваровУслугТовары.Номенклатура,
   РеализацияТоваровУслугТовары.ХарактеристикаНоменклатуры,
   ТоварыНаСкладахОстатки.КоличествоОстаток
ИЗ
   Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТоваровУслугТовары
   ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(&ДатаДокумента,) КАК ТоварыНаСкладахОстатки
   ПО РеализацияТоваровУслугТовары.Номенклатура = ТоварыНаСкладахОстатки.Номенклатура
   И РеализацияТоваровУслугТовары.ХарактеристикаНоменклатуры = ТоварыНаСкладахОстатки.ХарактеристикаНоменклатуры
   И РеализацияТоваровУслугТовары.Склад = ТоварыНаСкладахОстатки.Склад
ГДЕ РеализацияТоваровУслуг.Ссылка = &Ссылка

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

Ошибка 1. Синтаксическая ошибка компиляции (простая опечатка)

Начнем с самой очевидной проблемы, из-за которой данный запрос вообще откажется выполняться и выдаст ошибку на этапе компиляции. Обратим внимание на секцию ГДЕ:

ГДЕ РеализацияТоваровУслуг.Ссылка = &Ссылка

В секции ИЗ мы выбираем данные из табличной части документа и присваиваем ей псевдоним (алиас) РеализацияТоваровУслугТовары. Физическая таблица самого документа Документ.РеализацияТоваровУслуг в запросе отсутствует. Платформа 1С просто «не поймет», откуда брать поле РеализацияТоваровУслуг.Ссылка.

Решение: Для исправления этой опечатки необходимо обращаться к реквизиту Ссылка через правильный псевдоним табличной части:

ГДЕ РеализацияТоваровУслугТовары.Ссылка = &Ссылка

Ошибка 2. Прямое соединение физической таблицы с виртуальной (Глобальная архитектурная ошибка)

Это самая критическая ошибка с точки зрения производительности СУБД. В запросе выполняется ЛЕВОЕ СОЕДИНЕНИЕ физической таблицы табличной части документа с виртуальной таблицей остатков РегистрНакопления.ТоварыНаСкладах.Остатки.

Выясним причину, почему это делать категорически запрещено стандартами разработки «1С»:

  1. Отсутствие параметров виртуальной таблицы. Мы передаем в виртуальную таблицу только параметр даты &ДатаДокумента, но не передаем никаких отборов по номенклатуре, складам или характеристикам.
  2. Поведение СУБД. На уровне базы данных виртуальная таблица остатков представляет собой сложный подзапрос, который рассчитывает итоги на указанную дату. Поскольку в параметрах виртуальной таблицы нет фильтрации, СУБД будет вынуждена рассчитать и выгрузить из базы данных абсолютно все остатки по всем товарам и складам, которые есть в системе.
  3. Неэффективный план запроса. Оптимизатор СУБД не знает заранее объем данных, который вернет виртуальная таблица. При попытке соединить ее с физической таблицей документа оптимизатор построит неэффективный план (например, применит сканирование индекса Index Scan или всей таблицы Table Scan вместо быстрого поиска Index Seek). На реальной многопользовательской базе это приведет к мгновенной блокировке таблиц и зависанию системы.

Условие фильтрации ГДЕ РеализацияТоваровУслугТовары.Ссылка = &Ссылка применяется СУБД уже после того, как будут соединены гигантская таблица всех остатков и строки нашего документа. Это крайне неоптимально.

Решение: Согласно стандартам «1С», для решения этой проблемы необходимо использовать механизм Временных Таблиц (ВТ). Процесс оптимизации выглядит следующим образом:

Ошибка 3. Отсутствие предварительной группировки (Логическая ошибка контроля остатков)

Представим ситуацию: менеджер заполняет документ «Реализация товаров и услуг». По ошибке или в силу специфики бизнес-процесса он вносит один и тот же товар на один и тот же склад двумя разными строчками (например, в первой строке 5 штук товара «Смартфон», во второй строке — еще 3 штуки).

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

  1. Запрос вернет две строки.
  2. Для каждой из этих строк СУБД выполнит левое соединение с таблицей остатков.
  3. В обеих строчках в колонке КоличествоОстаток будет выведено одно и то же число — например, общий остаток на складе, равный 6 штукам.
  4. Если далее в коде проведения документа разработчик выполнит стандартную проверку вида КоличествоВСтроке > КоличествоОстаток, то проверка первой строки (5 < 6) пройдет успешно. Проверка второй строки (3 < 6) также пройдет успешно. Однако суммарно документ пытается списать 8 штук при наличии всего 6! Это неизбежно приведет к некорректному списанию товаров в «минус».

Решение: Перед соединением с остатками данные табличной части документа обязательно должны быть сгруппированы (свернуты) по ключевым полям: Номенклатура, ХарактеристикаНоменклатуры, Склад, а количество должно быть просуммировано.

Ошибка 4. Отсутствие обработки NULL-значений

В секции ВЫБРАТЬ напрямую запрашивается поле остатка:

ТоварыНаСкладахОстатки.КоличествоОстаток

Поскольку мы используем ЛЕВОЕ СОЕДИНЕНИЕ, для товаров, по которым на складе вообще нет остатков (их количество равно нулю), база данных вернет значение NULL.

Если в коде модуля проведения мы попытаемся выполнить математические операции с этим полем или сравнить его с числом (например, проверить, хватает ли остатка), система выдаст ошибку времени выполнения (Runtime Error), так как операции типа Число - NULL или сравнение числа с NULL в языке 1С недопустимы.

Решение: Все поля, получаемые из правой (присоединяемой) таблицы левого соединения, обязательно должны быть обработаны встроенной функцией ЕСТЬNULL(). Например:

ЕСТЬNULL(ТоварыНаСкладахОстатки.КоличествоОстаток, 0) КАК КоличествоОстаток

Ошибка 5. Некорректная дата для оперативного контроля остатков

В запросе используется параметр &ДатаДокумента:

ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(&ДатаДокумента,)

Если данный запрос используется в механизме проведения документа для проверки остатков, то использование чистой даты документа — это грубая методологическая ошибка:

Решение: Для оперативного контроля остатков при проведении документов используется специальный объект платформы — Граница. Нам необходимо передать в параметры запроса момент времени с исключением движений самого документа:

Новый Граница(Источник.МоментВремени(), ВидГраницы.Исключая) ---

Как написать этот запрос правильно? Разбор по шагам

Давайте соберем все наши выводы воедино и напишем эталонный, оптимизированный и безопасный пакетный запрос, который полностью соответствует стандартам разработки «1С».

Шаг 1. Создаем временную таблицу товаров документа с группировкой

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


ВЫБРАТЬ
   ТоварыТЧ.Склад КАК Склад,
   ТоварыТЧ.Номенклатура КАК Номенклатура,
   ТоварыТЧ.ХарактеристикаНоменклатуры КАК ХарактеристикаНоменклатуры,
   СУММА(ТоварыТЧ.Количество) КАК КоличествоВДокументе
ПОМЕСТИТЬ ВТ_ТоварыДокумента
ИЗ
   Документ.РеализацияТоваровУслуг.Товары КАК ТоварыТЧ
ГДЕ
   ТоварыТЧ.Ссылка = &Ссылка

СГРУППИРОВАТЬ ПО
   ТоварыТЧ.Склад,
   ТоварыТЧ.Номенклатура,
   ТоварыТЧ.ХарактеристикаНоменклатуры

ИНДЕКСИРОВАТЬ ПО
   Номенклатура,
   ХарактеристикаНоменклатуры,
   Склад
;

Обратите внимание: Мы добавили инструкцию ИНДЕКСИРОВАТЬ ПО. Индексирование полей временной таблицы, по которым в дальнейшем будет происходить связь (соединение), существенно ускоряет работу СУБД.

Шаг 2. Соединяем временную таблицу с виртуальной таблицей остатков

Теперь мы выбираем данные из нашей подготовленной временной таблицы ВТ_ТоварыДокумента и левым соединением подключаем таблицу остатков. При этом мы обязательно передаем фильтры (нашу номенклатуру, характеристики и склады) прямо в параметры виртуальной таблицы остатков, чтобы СУБД выбирала только нужные строки.


ВЫБРАТЬ
   ТоварыДокумента.Склад КАК Склад,
   ТоварыДокумента.Номенклатура КАК Номенклатура,
   ТоварыДокумента.ХарактеристикаНоменклатуры КАК ХарактеристикаНоменклатуры,
   ТоварыДокумента.КоличествоВДокументе КАК КоличествоВДокументе,
   ЕСТЬNULL(ТоварыНаСкладахОстатки.КоличествоОстаток, 0) КАК КоличествоОстаток
ИЗ
   ВТ_ТоварыДокумента КАК ТоварыДокумента
   ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(
       &Период,
       (Номенклатура, Характеристика, Склад) В 
           (ВЫБРАТЬ 
               ТоварыИзВТ.Номенклатура, 
               ТоварыИзВТ.ХарактеристикаНоменклатуры, 
               ТоварыИзВТ.Склад 
            ИЗ ВТ_ТоварыДокумента КАК ТоварыИзВТ)
   ) КАК ТоварыНаСкладахОстатки
   ПО ТоварыДокумента.Номенклатура = ТоварыНаСкладахОстатки.Номенклатура
   И ТоварыДокумента.ХарактеристикаНоменклатуры = ТоварыНаСкладахОстатки.Характеристика
   И ТоварыДокумента.Склад = ТоварыНаСкладахОстатки.Склад

Подведем итоги

Мы полностью переработали исходный некорректный запрос. Теперь он:

  1. Успешно компилируется и выполняется без синтаксических ошибок.
  2. Работает максимально быстро, так как СУБД рассчитывает остатки только по товарам из конкретного документа, а не по всему складу.
  3. Защищен от ошибок дублирования строк в документе благодаря предварительной группировке.
  4. Корректно обрабатывает пустые остатки с помощью функции ЕСТЬNULL(), возвращая 0 вместо NULL.

Именно такой подход к написанию запросов демонстрирует высокий профессиональный уровень разработчика и гарантирует стабильную работу информационной системы даже при очень больших объемах данных.

← На главную