Как в запросе 1С 7.7 получить остатки товаров без нулевых значений и избежать ошибок?

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

При работе в платформе 1С:Предприятие 7.7 (в среде разработчиков часто называемой «клюшками») программисты регулярно сталкиваются с ограничениями встроенного языка запросов. Одним из наиболее классических затруднений является попытка отсечь нулевые остатки товаров непосредственно в самом тексте запроса. В отличие от современных версий платформы (1С 8.x), где встроенный язык запросов максимально приближен к стандарту SQL, в «семерке» действуют совершенно иные механизмы.

В этой статье мы подробно разберем, почему возникает популярная ошибка Ошибка в выражении 'КонОстКол', проанализируем внутреннее поведение движка запросов 1С 7.7 и рассмотрим несколько надежных способов получения только актуальных (ненулевых) остатков товаров.

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

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


ТекстЗапроса = "
        |ПЕРИОД С ДатаОтчета По ДатаОтчета;
        |Склад     = Регистр.ОстаткиТоваров.Склад;
        |Товар     = Регистр.ОстаткиТоваров.Товар;
        |Кол     = Регистр.ОстаткиТоваров.ОстатокТовара;
        |Функция КонОстКол = КонОст(Кол);// 
        |Условие (КонОстКол>0);   
        |Условие (Товар в ВыбТовары);   
        |Условие (Склад в ВыбСклады);
        |Группировка Товар без групп все;";   
ЗапросОстатки.Выполнить(ТекстЗапроса);

При попытке выполнить этот код система выдает ошибку компиляции: Условие (КонОстКол <<?>> >0); Запрос[7] : Ошибка в выражении 'КонОстКол'. Здесь кроются сразу две ключевые проблемы — одна синтаксическая, а вторая глубокая архитектурная.

Проблема №1: Синтаксическая ошибка (пропущена точка с запятой)
В строке описания функции Функция КонОстКол = КонОст(Кол) в самом конце отсутствует обязательный символ точки с запятой ;. Из-за этого парсер запросов 1С склеивает эту строку со следующей, теряет контекст и спотыкается на операторе Условие.

Проблема №2: Использование агрегатной функции в секции «Условие»
Даже если мы поставим точку с запятой, запрос все равно выдаст ошибку. Дело в том, что в стандартном («черном») запросе 1С 7.7 нельзя использовать имя переменной, рассчитываемой через агрегатную функцию (в данном случае КонОстКол), внутри оператора Условие(...).

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

---

Как правильно решить задачу? Разбор практических способов

Рассмотрим несколько рабочих подходов к решению этой проблемы — от простых базовых исправлений до профессиональной оптимизации.

Способ А: Фильтрация при обходе результатов (Рекомендуемый и самый безопасный)

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

Давайте посмотрим, как будет выглядеть правильный программный код:


// Формируем чистый и корректный запрос
ТекстЗапроса = "
|ПЕРИОД С ДатаОтчета По ДатаОтчета;
|Склад     = Регистр.ОстаткиТоваров.Склад;
|Товар     = Регистр.ОстаткиТоваров.Товар;
|Кол       = Регистр.ОстаткиТоваров.ОстатокТовара;
|Функция КонОстКол = КонОст(Кол); // Точка с запятой обязательна!
|Условие (Товар в ВыбТовары);   
|Условие (Склад в ВыбСклады);
|Группировка Товар без групп; // Убрали ключевое слово 'все'
|";

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

Преимущества этого способа:

  1. Абсолютная математическая точность. Вы гарантированно получите только те позиции, у которых реальный конечный остаток больше нуля.
  2. Безопасность для других показателей. Если вам в этом же запросе понадобятся, к примеру, обороты (приход или расход), этот цикл никак не исказит их расчет, в отличие от попыток жесткой фильтрации внутри запроса.
  3. Высокая скорость работы. Платформа не тратит ресурсы на сложные и некорректные вычисления внутри базы данных.

---

Роль ключевого слова «все» в группировке

Обратите внимание на строку группировки в нашем примере: Группировка Товар без групп;. Мы намеренно убрали ключевое слово все (в исходном запросе автора было Группировка Товар без групп все;).

Выясним разницу:

---

Способ Б: «Лайфхак» с обращением через объект запроса (С осторожностью!)

В обсуждении на форуме (сообщение №6) был предложен интересный обходной путь:

|Условие (ЗапросОстатки.КонОстКол > 0);

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

Существенные минусы «лайфхака»:

Резюме по Способу Б: Использовать данный трюк можно только в самых простых и некритичных отчетах, где получается исключительно конечный остаток, а объем данных минимален.

---

Способ В: Использование прямых SQL-запросов (Для баз на MS SQL Server)

Если ваша информационная база 1С 7.7 работает в файловом режиме (DBF), то Способ А является для вас единственным верным решением. Однако, если вы администрируете и разрабатываете крупную систему на базе MS SQL Server, штатные запросы 1С 7.7 могут стать «бутылочным горлышком» системы.

Штатный механизм запросов 1С 7.7 транслирует «черные» запросы в SQL крайне неэффективно, зачастую вытягивая огромные массивы данных на сторону клиента для локальной обработки. В профессиональной среде для решения задач получения остатков без нулевых значений используют внешние компоненты, такие как 1С++.

Прямой SQL-запрос к таблице итогов регистра накопления (например, к таблице остатков RG*) позволяет выполнить моментальную фильтрацию данных силами самого сервера СУБД. На стороне SQL это решается использованием конструкции HAVING SUM(...) > 0.

Рассмотрим пример построения прямого SQL-запроса через класс ODBCRecordset компоненты 1С++:


// Пример использования внешней компоненты 1С++ для прямого запроса
БазаДанных = СоздатьОбъект("ODBCRecordset");

// Строим эффективный SQL-запрос к таблице итогов регистра ОстаткиТоваров
ТекстSQL = "
|SELECT 
|   Рег.SP_Склад as [Склад $Справочник.Склады],
|   Рег.SP_Товар as [Товар $Справочник.Номенклатура],
|   SUM(Рег.SP_Количество) as Остаток
|FROM 
|   $РегистрИтоги.ОстаткиТоваров Рег
|WHERE 
|   Рег.Period = :ВыбПериод
|GROUP BY 
|   Рег.SP_Склад, Рег.SP_Товар
|HAVING 
|   SUM(Рег.SP_Количество) > 0
|";

БазаДанных.УстановитьТекстовыйПараметр("ВыбПериод", НачалоМесяца(ДатаОтчета));
ТаблицаРезультатов = БазаДанных.ВыполнитьИнструкцию(ТекстSQL);
// В ТаблицаРезультатов окажутся только товары с остатком строго больше 0

Такой запрос на больших базах данных выполняется в десятки и сотни раз быстрее стандартного, так как СУБД передает на клиентский компьютер только готовую отфильтрованную таблицу без лишнего «мусора».

---

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

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

  1. Всегда проверяйте синтаксис ваших функций внутри запроса — не забывайте ставить точку с запятой.
  2. Не пытайтесь наложить фильтр Условие на агрегатные функции (КонОст, НачОст, Приход, Расход) непосредственно в теле запроса.
  3. Для автоматического уменьшения выборки убирайте ключевое слово все из параметров группировки.
  4. Осуществляйте финальную фильтрацию нулевых остатков простым условием Если <...> <= 0 Тогда Продолжить; КонецЕсли; при обходе выборки в цикле.
← На главную