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

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

Часто в практике разработки на платформе 1С 7.7 возникает классическая задача: получить из регистра остатки товаров, по которым за выбранный период были движения (приход или расход). На первый взгляд, задача кажется тривиальной, однако при попытке решить ее штатным «черным» запросом разработчики сталкиваются с тем, что расчет остатков ломается. Рассмотрим подробнее, почему так происходит, и разберем по шагам рабочие способы решения этой проблемы.

Почему в штатном («черном») запросе условие на обороты ломает остатки

Давайте проанализируем ситуацию на примере классического кода. Многие разработчики пытаются написать примерно следующий штатный запрос:


|Период С ДатаНач По ДатаОконч;
|Товар = Регистр.Товары.Номенклатура;
|Количество = Регистр.Товары.Количество;
|Функция ПриходК = Приход(Количество);
|Функция РасходК = Расход(Количество);
|Функция КонОстК = КонОст(Количество);
|Условие((Запрос.РасходК<>0) ИЛИ (Запрос.ПриходК<>0));

При выполнении этого кода мы видим, что остатки на конец периода (КонОстК) перестают выводиться корректно или вовсе обнуляются. Выясним причину такого поведения.

Архитектура штатного механизма запросов 1С 7.7 устроена так, что функции (например, Приход или Расход) и расчет остатков (КонОст) вычисляются «на лету» в момент перебора физических записей таблиц итогов и движений. Если в тексте запроса мы накладываем условие на функцию оборота, парсер 1С применяет этот фильтр на самом нижнем уровне выборки. В результате отсекаются те физические записи, которые критически необходимы движку для математического расчета правильного начального и конечного остатка. В штатном запросе 7.7 технически невозможно разделить фильтрацию уже готового конечного результата от фильтрации исходных данных для расчета. Итоговые цифры неизбежно искажаются.

Вариант решения 1: Обработка результатов запроса в Таблице Значений

Разберем первый рабочий способ, который не требует использования сторонних SQL-компонент. Суть метода заключается в том, чтобы разнести получение данных и их фильтрацию на два этапа.

  1. Мы выполняем штатный запрос без фильтра по оборотам. Это гарантирует, что КонОст посчитается абсолютно верно.
  2. Выгружаем плоский результат запроса в ТаблицаЗначений.
  3. Обходим полученную таблицу и удаляем лишние строки, где обороты равны нулю.

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


ТабРез = СоздатьОбъект("ТаблицаЗначений");
Запрос.Выгрузить(ТабРез, 0, 0);

Для Индекс = -ТабРез.КоличествоСтрок() По -1 Цикл
    ТекСтрока = -Индекс;
    ТабРез.ПолучитьСтрокуПоНомеру(ТекСтрока);
    Если (ТабРез.ПриходК = 0) И (ТабРез.РасходК = 0) Тогда
        ТабРез.УдалитьСтроку(ТекСтрока);
    КонецЕсли;
КонецЦикла;

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

Вариант решения 2: Использование Индексированной Таблицы Значений (ИТЗ)

Рассмотрим более производительный вариант первого способа. Если в базе установлено расширение 1С++, мы можем использовать объект ИндексированнаяТаблица (ИТЗ). В отличие от обычного перебора, ИТЗ позволяет сделать фильтрацию практически мгновенно.

  1. Выполняем штатный запрос без фильтра.
  2. Выгружаем результат в объект ИТЗ.
  3. Добавляем индекс по колонкам оборотов (например, по колонке ПриходК).
  4. Устанавливаем быстрый фильтр на нулевые значения оборотов.
  5. Удаляем отфильтрованные строки одной командой.
  6. Снимаем фильтр и группируем оставшиеся данные по номенклатуре.

Этот способ отлично спасает, когда база данных работает в формате DBF, и использование полноценных SQL-запросов затруднено, но требуется высокая скорость обработки.

Вариант решения 3: Использование прямых запросов (SQLite / 1C++)

Проанализируем самый мощный и быстрый инструмент — прямые SQL-запросы через компоненты 1С++ или 1SQLite. Переход на прямые запросы позволяет решить задачу элегантно и одним запросом.

В компоненте 1С++ реализован механизм виртуальных таблиц, аналогичный тому, что используется в современных версиях 1С 8.х. Мы можем использовать макрос виртуальной таблицы, например $РегистрОстаткиОбороты.Товары(...). В параметрах этой таблицы задаются условия объединения, периодичность и нужные фильтры. Компонента самостоятельно сгенерирует оптимальный SQL-код, который обратится к физическим таблицам итогов (RG) и движений (RA), и вернет правильные остатки и обороты.

Главное преимущество SQL — мы можем применить секцию HAVING или обернуть результат во вложенный запрос и отфильтровать итоговые данные WHERE (Приход <> 0 OR Расход <> 0). При этом расчет остатков не пострадает, так как SQL-сервер сначала произведет агрегацию (группировку) данных, а уже затем отбросит строки с нулевыми оборотами.

Автор обсуждаемой темы провел тестирование на реальной базе за месяц. Результаты показали значительное ускорение:
Штатный черный запрос выполнялся около 18 секунд.
Прямой запрос SQLite выполнился всего за 3.4 - 4.2 секунды.

Почему отличается количество строк в «черном» и прямом запросах?

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

  1. Нулевые строки: Штатный движок 1С 7.7 автоматически скрывает строки результата, по которым и остатки, и обороты свернулись в абсолютный ноль (например, товар пришел и полностью ушел, а начального остатка не было). В SQL-запросе, если вы явно не прописали отсечение нулевых строк, они попадут в финальную выборку.
  2. Границы времени: Штатный запрос при указании По ДатаОконч автоматически раздвигает границу расчета до конца дня (23:59:59) или до Точки Актуальности (ТА). Разработчики прямых запросов часто забывают программно сместить границу периода на конец дня, из-за чего могут «выпасть» последние документы периода.
  3. Физическое удаление в DBF: В файловых базах DBF часто остаются физически не удаленные записи (помеченные на удаление звездочкой в самом формате DBF, а не логикой 1С). Штатный механизм 1С их автоматически игнорирует, а прямой SQL-запрос может их прочитать, если не заданы флаги проверки удаленных строк.

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

← На главную