При разработке алгоритмов проведения документов, особенно в учебных или самописных конфигурациях, мы часто сталкиваемся с ситуацией, когда логически верный запрос возвращает пустой результат. Рассмотрим распространенный сценарий: вы создаете запрос с помещением данных во временную таблицу, всё проходит успешно, но при попытке соединить эту таблицу с виртуальной таблицей остатков (для контроля списания) итоговая выборка оказывается пустой.
В этой статье мы подробно разберем, почему так происходит, проанализируем методики проведения документов и выясним, как порядок действий в коде влияет на результат запроса.
Давайте представим ситуацию, с которой столкнулся автор вопроса. У нас есть следующая последовательность действий:
МенеджерВременныхТаблиц для передачи этой ВТ в следующий запрос.Многие разработчики начинают искать причину в параметрах виртуальной таблицы (период, граница) или в типах данных, но корень проблемы часто лежит в самой логике проведения документа.
Чтобы понять причину ошибки, нам необходимо вспомнить теорию разработки в 1С. Существует две основные методики контроля остатков при проведении документов. Давайте их проанализируем.
При этом подходе алгоритм выглядит так:
В этом случае запрос должен вернуть текущие положительные остатки. Если запрос пустой — значит, на складе действительно 0.
Этот подход стал стандартом для высоконагруженных систем и типовых конфигураций на платформе 8.2 и 8.3. Алгоритм следующий:
Проанализируем ситуацию автора. В запросе использовалось условие для поиска отрицательных остатков. Это характерно именно для второй («новой») методики.
Главная ошибка: Разработчик написал код запроса, но забыл выполнить физическую запись движений в регистр перед выполнением этого запроса.
Давайте проследим логику системы:
Остатки с соединением по временной таблице.КоличествоОстаток < 0 не выполняется ни для одной строки.Таким образом, пустой результат в данном случае — это не техническая ошибка языка запросов, а логическая ошибка в последовательности команд.
Чтобы механизм контроля отрицательных остатков работал корректно, необходимо явно записать движения перед проверкой. Рассмотрим, как должен выглядеть правильный код.
Обратите внимание на метод Записать().
Процедура ОбработкаПроведения(Отказ, РежимПроведения)
// 1. Сначала формируем движения (списываем товар "в минус", если его не хватает)
// Очищаем старые движения, если перепроводим документ
Движения.ТоварыНаСкладах.Записывать = Истина;
Движения.ТоварыНаСкладах.Очистить();
Для Каждого ТекСтрокаТовары Из Товары Цикл
Движение = Движения.ТоварыНаСкладах.Добавить();
Движение.ВидДвижения = ВидДвиженияНакопления.Расход;
Движение.Период = Дата;
Движение.Номенклатура = ТекСтрокаТовары.Номенклатура;
Движение.Количество = ТекСтрокаТовары.Количество;
// Заполняем остальные измерения и ресурсы
КонецЦикла;
// КРИТИЧЕСКИ ВАЖНЫЙ МОМЕНТ:
// Физически записываем движения в базу данных, чтобы они повлияли на остатки
Движения.ТоварыНаСкладах.Записать();
// 2. Устанавливаем блокировку (для надежности при параллельной работе)
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрНакопления.ТоварыНаСкладах");
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
ЭлементБлокировки.ИсточникДанных = Товары;
ЭлементБлокировки.ИспользоватьИзИсточника("Номенклатура", "Номенклатура");
Блокировка.Заблокировать();
// 3. Теперь выполняем запрос на проверку отрицательных остатков
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТоварыНаСкладахОстатки.Номенклатура КАК Номенклатура,
| ТоварыНаСкладахОстатки.КоличествоОстаток КАК КоличествоОстаток
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(
| &Граница,
| Номенклатура В
| (ВЫБРАТЬ
| Т.Номенклатура
| ИЗ
| Документ.РеализацияТоваров.Товары КАК Т
| ГДЕ
| Т.Ссылка = &Ссылка)) КАК ТоварыНаСкладахОстатки
|ГДЕ
| ТоварыНаСкладахОстатки.КоличествоОстаток < 0";
// Обратите внимание на Границу. Мы должны включить движения самого документа.
Запрос.УстановитьПараметр("Граница", Новый Граница(МоментВремени(), ВидГраницы.Включая));
Запрос.УстановитьПараметр("Ссылка", Ссылка);
Результат = Запрос.Выполнить();
// 4. Если запрос не пустой — значит ушли в минус
Если Не Результат.Пустой() Тогда
Отказ = Истина;
Выборка = Результат.Выбрать();
Пока Выборка.Следующий() Цикл
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "Не хватает товара " + Выборка.Номенклатура +
" в количестве " + (-1 * Выборка.КоличествоОстаток);
Сообщение.Сообщить();
КонецЦикла;
КонецЕсли;
КонецПроцедуры
В ходе обсуждения проблемы также был затронут вопрос о параметре «Период» (или «Граница») в виртуальной таблице остатков. Давайте уточним этот момент, так как он тоже влияет на результат запроса.
Если мы проводим документ текущей датой (оперативно), то система должна контролировать самые актуальные остатки. Однако, при использовании «новой методики» (описанной выше), мы проверяем остатки после записи движений документа. Поэтому нам необходимо использовать параметр Граница, включающий момент времени документа.
Если не указать период вообще, система возьмет таблицу актуальных итогов. Если движения уже записаны, итоги обновятся, и проверка сработает. Но использование Границы считается более надежным и универсальным подходом.
Здесь использование Границы строго обязательно. Нам нужно получить состояние склада именно на ту секунду, когда документ встал на ось времени, включая его собственные движения.
Иногда разработчики ошибочно передают в параметр периода пустую дату (Дата(1,1,1)) или думают, что если не передать параметр, это равносильно «началу времен». Это неверно.
&Период = Дата(1,1,1) — вы получите остатки на 1 января 1 года нашей эры. Их там нет. Результат будет пустым.&Период — вы получите остатки на точку актуальности (на текущий момент).Хотя в данном случае проблема была в логике проведения, не стоит забывать о правилах работы с менеджером временных таблиц, чтобы избежать пустых выборок в будущем:
МенеджерВременныхТаблиц должен существовать, пока вы выполняете запросы. Если переменная менеджера будет переопределена или уничтожена, таблицы исчезнут.ПОЛНОЕ СОЕДИНЕНИЕ или ВНУТРЕННЕЕ СОЕДИНЕНИЕ. Для надежности используйте конструкцию ВЫРАЗИТЬ(... КАК ...) при создании ВТ.ИНДЕКСИРОВАТЬ ПО...). Это не влияет на пустоту выборки, но критически важно для производительности.Если ваш запрос на контроль отрицательных остатков возвращает пустоту:
Движения.ИмяРегистра.Записать();.Новый Граница(МоментВремени(), ВидГраницы.Включая).