Почему возвращается пустая выборка при контроле отрицательных остатков с использованием временной таблицы?

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

При разработке алгоритмов проведения документов, особенно в учебных или самописных конфигурациях, мы часто сталкиваемся с ситуацией, когда логически верный запрос возвращает пустой результат. Рассмотрим распространенный сценарий: вы создаете запрос с помещением данных во временную таблицу, всё проходит успешно, но при попытке соединить эту таблицу с виртуальной таблицей остатков (для контроля списания) итоговая выборка оказывается пустой.

В этой статье мы подробно разберем, почему так происходит, проанализируем методики проведения документов и выясним, как порядок действий в коде влияет на результат запроса.

Суть проблемы

Давайте представим ситуацию, с которой столкнулся автор вопроса. У нас есть следующая последовательность действий:

  1. Мы формируем запрос, который выбирает товары из табличной части документа.
  2. Помещаем этот список во временную таблицу (ВТ).
  3. Используем МенеджерВременныхТаблиц для передачи этой ВТ в следующий запрос.
  4. Во втором запросе пытаемся получить остатки по товарам из ВТ и наложить условие, чтобы найти отрицательные остатки (ситуация нехватки товара).
  5. Результат второго запроса — пустой, хотя мы ожидаем увидеть позиции, которых не хватает на складе.

Многие разработчики начинают искать причину в параметрах виртуальной таблицы (период, граница) или в типах данных, но корень проблемы часто лежит в самой логике проведения документа.

Две методики проведения документа

Чтобы понять причину ошибки, нам необходимо вспомнить теорию разработки в 1С. Существует две основные методики контроля остатков при проведении документов. Давайте их проанализируем.

1. «Старая» методика (Сначала проверяем, потом списываем)

При этом подходе алгоритм выглядит так:

В этом случае запрос должен вернуть текущие положительные остатки. Если запрос пустой — значит, на складе действительно 0.

2. «Новая» методика (Сначала списываем, потом проверяем минус)

Этот подход стал стандартом для высоконагруженных систем и типовых конфигураций на платформе 8.2 и 8.3. Алгоритм следующий:

Разбор ошибки: почему запрос пустой?

Проанализируем ситуацию автора. В запросе использовалось условие для поиска отрицательных остатков. Это характерно именно для второй («новой») методики.

Главная ошибка: Разработчик написал код запроса, но забыл выполнить физическую запись движений в регистр перед выполнением этого запроса.

Давайте проследим логику системы:

  1. Документ начал проводиться (транзакция открыта).
  2. Сформирована временная таблица «ТоварыДокумента».
  3. Выполняется запрос к виртуальной таблице Остатки с соединением по временной таблице.
  4. В условии запроса сказано: «Верни мне те записи, где КоличествоОстаток < 0».
  5. Но так как движения самого документа еще не записаны в базу данных, регистр "не знает" о том, что мы хотим списать товар.
  6. Система видит старые (положительные) остатки.
  7. Условие КоличествоОстаток < 0 не выполняется ни для одной строки.
  8. Результат: Пустая выборка.

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

Правильная реализация алгоритма

Чтобы механизм контроля отрицательных остатков работал корректно, необходимо явно записать движения перед проверкой. Рассмотрим, как должен выглядеть правильный код.

Обратите внимание на метод Записать().


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

    // Обратите внимание на Границу. Мы должны включить движения самого документа.
    Запрос.УстановитьПараметр("Граница", Новый Граница(МоментВремени(), ВидГраницы.Включая));
    Запрос.УстановитьПараметр("Ссылка", Ссылка);
    
    Результат = Запрос.Выполнить();
    
    // 4. Если запрос не пустой — значит ушли в минус
    Если Не Результат.Пустой() Тогда
        Отказ = Истина;
        Выборка = Результат.Выбрать();
        Пока Выборка.Следующий() Цикл
            Сообщение = Новый СообщениеПользователю;
            Сообщение.Текст = "Не хватает товара " + Выборка.Номенклатура + 
                              " в количестве " + (-1 * Выборка.КоличествоОстаток);
            Сообщение.Сообщить();
        КонецЦикла;
    КонецЕсли;

КонецПроцедуры

Важные нюансы параметров виртуальной таблицы

В ходе обсуждения проблемы также был затронут вопрос о параметре «Период» (или «Граница») в виртуальной таблице остатков. Давайте уточним этот момент, так как он тоже влияет на результат запроса.

  1. Оперативное проведение:

    Если мы проводим документ текущей датой (оперативно), то система должна контролировать самые актуальные остатки. Однако, при использовании «новой методики» (описанной выше), мы проверяем остатки после записи движений документа. Поэтому нам необходимо использовать параметр Граница, включающий момент времени документа.

    Если не указать период вообще, система возьмет таблицу актуальных итогов. Если движения уже записаны, итоги обновятся, и проверка сработает. Но использование Границы считается более надежным и универсальным подходом.

  2. Неоперативное проведение (задним числом):

    Здесь использование Границы строго обязательно. Нам нужно получить состояние склада именно на ту секунду, когда документ встал на ось времени, включая его собственные движения.

  3. Ошибка «Пустая дата»:

    Иногда разработчики ошибочно передают в параметр периода пустую дату (Дата(1,1,1)) или думают, что если не передать параметр, это равносильно «началу времен». Это неверно.

    • Если передать &Период = Дата(1,1,1) — вы получите остатки на 1 января 1 года нашей эры. Их там нет. Результат будет пустым.
    • Если не передавать параметр &Период — вы получите остатки на точку актуальности (на текущий момент).

Рекомендации по работе с Временными Таблицами (ВТ)

Хотя в данном случае проблема была в логике проведения, не стоит забывать о правилах работы с менеджером временных таблиц, чтобы избежать пустых выборок в будущем:

Резюме

Если ваш запрос на контроль отрицательных остатков возвращает пустоту:

  1. Проверьте, используете ли вы «новую методику» (ищете где Количество < 0).
  2. Если да — убедитесь, что перед запросом выполняется команда Движения.ИмяРегистра.Записать();.
  3. Убедитесь, что в параметрах виртуальной таблицы используется Новый Граница(МоментВремени(), ВидГраницы.Включая).
← На главную