Как в запросе 1С 7.7 получить только отрицательные остатки?

Программист 1С v7.7 Управленческий учет IT и автоматизация бизнеса
← На главную

При построении отчетов в платформе 1С:Предприятие 7.7 разработчики часто сталкиваются с задачей отбора позиций номенклатуры, по которым образовался отрицательный конечный остаток. Попытка применить стандартную конструкцию вида |Условие(КолКонОст < 0); приводит к ошибке или некорректным результатам.

Разберем причину такого поведения платформы. Секция Условие() в черных запросах 1С 7.7 работает как фильтр отдельных движений (аналог инструкции WHERE в SQL). В языке запросов 1С 7.7 отсутствует прямой аналог конструкции HAVING, позволяющий фильтровать уже сгруппированные и посчитанные агрегатные функции, такие как КонОст() или Сумма(). Если написать условие на ресурс регистра, система проверит значение этого ресурса в каждой конкретной записи движения, а не итоговый конечный остаток по позиции.

Рассмотри эффективные и проверенные способы решения этой проблемы.

Способ 1. Двухэтапный запрос с использованием списка значений

Наиболее надежный и правильный способ на встроенном языке 1С 7.7 — разделение задачи на два этапа. Сначала первым запросом мы собираем список позиций с отрицательным остатком, а вторым запросом формируем итоговый отчет с правильным расчетом всех иерархических группировок.

Рассмотрим пошагово реализацию этого алгоритма:

  1. Выполняем первичный запрос с детальной группировкой по номенклатуре.
  2. Обходим результат первичного запроса и добавляем элементы, у которых конечный остаток меньше нуля, в объект СписокЗначений.
  3. Передаем сформированный список во второй запрос через секцию Условие(Номенклатура в СписокОтрицательных).

Посмотрим на пример кода:


Перем Запрос1, Запрос2, ТекстЗапроса1, ТекстЗапроса2, СписокОтрицательных;

СписокОтрицательных = СоздатьОбъект("СписокЗначений");

// Этап 1: Отобрать номенклатуру с отрицательным остатком
Запрос1 = СоздатьОбъект("Запрос");
ТекстЗапроса1 = "
|Период с НачДата по КонДата;
|Номенклатура = Регистр.ОстаткиТМЦ.Номенклатура;
|Количество = Регистр.ОстаткиТМЦ.Количество;
|Функция КолКонОст = КонОст(Количество);
|Группировка Номенклатура Без Групп;
|";

Если Запрос1.Выполнить(ТекстЗапроса1) = 1 Тогда
    Пока Запрос1.Группировка("Номенклатура") = 1 Цикл
        Если Запрос1.КолКонОст < 0 Тогда
            СписокОтрицательных.ДобавитьЗначение(Запрос1.Номенклатура);
        КонецЕсли;
    КонецЦикла;
КонецЕсли;

// Этап 2: Сформировать итоговый запрос с иерархией и итогами
Запрос2 = СоздатьОбъект("Запрос");
ТекстЗапроса2 = "
|Период с НачДата по КонДата;
|Номенклатура = Регистр.ОстаткиТМЦ.Номенклатура;
|Количество = Регистр.ОстаткиТМЦ.Количество;
|Функция КолКонОст = КонОст(Количество);
|Группировка Номенклатура;
|Условие(Номенклатура в СписокОтрицательных);
|";

Если Запрос2.Выполнить(ТекстЗапроса2) = 1 Тогда
    // Вывод результатов запроса в печатную форму
КонецЕсли;

Способ 2. Вызов внешней функции внутри запроса

Другой интересный вариант — использование пользовательской функции модуля внутри конструкции Функция ... = Сумма(...). Язык запросов 1С 7.7 позволяет вызывать функции глобального модуля или модуля формы при вычислении агрегатов.

Проанализируем пример реализации данного метода:


Функция ПолучитьОтрицОстаток(Товар, Склад)
    Ост = Регистр.ОстаткиТоваров.СводныйОстаток(Товар, Склад, "ОстатокТовара");
    Возврат ?(Ост < 0, Ост, 0);
КонецФункции

Процедура Сформировать()
    Перем Запрос, ТекстЗапроса;
    Запрос = СоздатьОбъект("Запрос");
    
    ТекстЗапроса = "
    |Товар = Регистр.ОстаткиТоваров.Товар;
    |Склад = Регистр.ОстаткиТоваров.Склад;
    |ОстатокТовара = Регистр.ОстаткиТоваров.ОстатокТовара;
    |Функция ОстатокТовараКонОст = КонОст(ОстатокТовара);
    |Функция ТолькоОтрицательные = Сумма(ПолучитьОтрицОстаток(Товар, Склад));
    |Группировка Склад Без Групп;
    |Группировка Товар Без Групп;
    |";
    
    Если Запрос.Выполнить(ТекстЗапроса) = 1 Тогда
        // Обработка результатов
    КонецЕсли;
КонецПроцедуры

Важно: этот способ вычисляет сводный остаток на текущий момент (или точку актуальности) с помощью метода СводныйОстаток(), поэтому его удобно применять для оперативности, но нужно учитывать логику работы с временными периодами.

Способ 3. Отбор при обходе выборки запроса

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

Рассмотрим пример кода фильтрации при выводе:


Пока Запрос.Группировка("Склад") = 1 Цикл
    Пока Запрос.Группировка("Номенклатура") = 1 Цикл
        // Проверяем конечное значение агрегатной функции
        Если Запрос.КолКонОст < 0 Тогда
            Таб.ВывестиСекцию("Строка");
        КонецЕсли;
    КонецЦикла;
КонецЦикла;

Способ 4. Использование прямых SQL-запросов (1С++)

В случаях, когда обработка выполняется на больших объемах данных и критична скорость работы, эффективным решением становится использование внешних компонентов (например, 1С++) для выполнения прямых SQL-запросов к базам данных.

При использовании прямых запросов отбор отрицательных остатков выполняется на стороне СУБД с помощью стандартной инструкции HAVING SUM(...) < 0. Это позволяет мгновенно отсечь лишние данные из виртуальных таблиц остатков (таблиц RG*) еще до передачи данных на клиентский ПК.

Подводя итог: для типовых отчетов на 1С 7.7 оптимальным выбором является двухэтапный запрос через СписокЗначений, так как он гарантирует корректность иерархических итогов и не требует внешних компонент.

← На главную