При разработке отчетов в системе 1С:Предприятие 7.7 начинающие программисты часто сталкиваются с тем, что привычная логика построения запросов (характерная для SQL или 1С 8.x) здесь не работает. Одной из типичных задач является получение данных из регистров (например, остатков или продаж) только по тем позициям номенклатуры, которые фигурировали в определенных документах за тот же период. Попытка сделать это «в лоб» через описание нескольких источников в одном запросе обычно приводит к тому, что фильтрация просто игнорируется или выдает пустой результат.
В этой статье мы подробно разберем, почему стандартный механизм «черных запросов» не позволяет связывать регистры и документы напрямую через условие, и рассмотрим правильные методики решения этой задачи.
Рассмотрим ситуацию, с которой столкнулся автор темы. В запросе были указаны два источника данных: регистры (ОстаткиТМЦ, Продажи) и документ (ПоступлениеТМЦРозница). Логика автора заключалась в том, чтобы через переменную Номенклатура2 ограничить основную выборку. Однако система выдала всю номенклатуру без учета фильтра.
Выясним причину такого поведения движка 1С 7.7. Основная проблема заключается в архитектуре механизма запросов того времени:
Номенклатура2) получает значение только в тот момент, когда движок обрабатывает конкретную запись из источника (в данном случае — строку документа). Когда запрос перебирает записи регистров для подсчета итогов, контекст документа отсутствует. Следовательно, в момент обработки движений регистра переменная Номенклатура2 пуста, и сравнение Номенклатура в Номенклатура2 не имеет смысла для системы.СписокЗначений. Он не предназначен для сравнения двух внутренних динамических переменных запроса из разных источников.Посмотрим на исходный (нерабочий) код запроса, чтобы понимать, как делать не стоит:
// Пример нерабочего запроса
ТекстЗапроса =
"//{{ЗАПРОС(Сформировать)
|Период с ВыбНачПериода по ВыбКонПериода;
|Номенклатура = Регистр.ОстаткиТМЦ.Номенклатура, Регистр.Продажи.Номенклатура;
|Номенклатура2 = Документ.ПоступлениеТМЦРозница.Номенклатура;
|Количество = Регистр.Продажи.Количество;
|Функция КоличествоПрод = Сумма(Количество);
|Группировка Номенклатура без групп;
|Условие(Номенклатура в Номенклатура2); // ОШИБКА: это условие не сработает как ожидается
|";
Самый надежный и правильный способ реализации такой логики в 1С 7.7 — это разделение задачи на два этапа. Сначала мы определим список нужной нам номенклатуры, а затем используем этот список как фильтр во втором запросе. Разберем этот алгоритм по шагам.
Шаг 1: Сбор номенклатуры из документов. Создадим объект СписокЗначений и заполним его уникальными ссылками на номенклатуру, которая встречается в документах за выбранный период.
СписокТоваровИзПрихода = СоздатьОбъект("СписокЗначений");
ЗапросПриход = СоздатьОбъект("Запрос");
ТекстЗапросаПриход =
"|Период с ВыбНачПериода по ВыбКонПериода;
|Товар = Документ.ПоступлениеТМЦРозница.Номенклатура;
|Группировка Товар без групп;
|";
Если ЗапросПриход.Выполнить(ТекстЗапросаПриход) = 1 Тогда
Пока ЗапросПриход.Группировка(1) = 1 Цикл
СписокТоваровИзПрихода.ДобавитьЗначение(ЗапросПриход.Товар);
КонецЦикла;
КонецЕсли;
Шаг 2: Выполнение основного запроса с фильтрацией. Теперь мы передаем полученный список в условие второго запроса. В этом случае 1С корректно отфильтрует движения регистров, так как список является внешним объектом с фиксированным набором значений.
ЗапросПродажи = СоздатьОбъект("Запрос");
ТекстЗапросаПродажи =
"|Период с ВыбНачПериода по ВыбКонПериода;
|Номенклатура = Регистр.Продажи.Номенклатура, Регистр.ОстаткиТМЦ.Номенклатура;
|КолПрод = Регистр.Продажи.Количество;
|Функция ПродСумма = Сумма(КолПрод);
|Группировка Номенклатура без групп;
|Группировка Неделя;
|Условие(Номенклатура в СписокТоваровИзПрихода); // Фильтр работает корректно
|";
Если ЗапросПродажи.Выполнить(ТекстЗапросаПродажи) = 0 Тогда
Возврат;
КонецЕсли;
Этот метод гарантирует точность данных и работает достаточно быстро на средних объемах базы.
Существует еще один способ — использование внешней функции в тексте запроса. Проанализируем этот вариант. Мы можем создать в модуле функцию, которая проверяет наличие текущей номенклатуры в документах поступления.
Функция ПроверитьНаличиеВПоступлении(ТекущийТовар)
// Здесь должна быть логика проверки, например, через поиск в заранее подготовленном списке или индексированной таблице
Если СписокТоваровИзПрихода.НайтиЗначение(ТекущийТовар) > 0 Тогда
Возврат 1;
КонецЕсли;
Возврат 0;
КонецФункции
В тексте запроса это будет выглядеть так: Условие(ПроверитьНаличиеВПоступлении(Номенклатура) = 1);.
Внимание: Мы крайне не рекомендуем использовать этот метод на больших базах данных. Причина в том, что функция будет вызываться для каждой записи регистра, попавшей в период запроса. Если записей десятки тысяч, формирование отчета может затянуться на часы.
Если производительность штатных средств вас не устраивает (например, база работает в формате SQL и содержит миллионы строк), стоит рассмотреть использование внешних компонент, таких как 1С++.
Прямой запрос к таблицам базы данных (таблицы RG*, RA* для регистров и DT* для строк документов) позволит выполнить операцию INNER JOIN. Это самый быстрый способ, так как вся работа по фильтрации перекладывается на плечи SQL-сервера, а в 1С возвращается уже готовый, отфильтрованный результат.
В исходном вопросе упоминалась группировка Неделя все вошедшиеВЗапрос. Важно помнить, что в 1С 7.7 такая конструкция имеет свои особенности:
Проанализировав ситуацию, мы видим, что ключ к успеху в 1С 7.7 — это четкое понимание того, как система обрабатывает переменные. Используя метод двух запросов, вы сможете решать задачи любой сложности по связыванию данных из различных источников, сохраняя при этом стабильность и предсказуемость работы конфигурации Торговля и Склад.