Как передать результат одного запроса в другой в 1С 7.7 через Список Значений?

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

Разработчики, работающие с платформой 1С:Предприятие 7.7, часто сталкиваются с ограничениями встроенного языка запросов. Одно из наиболее существенных ограничений — полное отсутствие поддержки вложенных запросов и конструкций типа ГДЕ Клиент В (ВЫБРАТЬ...). Если нам необходимо решить двухэтапную задачу (например, сначала найти всех клиентов, которым отгружался определенный товар, а затем по этим клиентам получить весь список расходных накладных), нам приходится строить алгоритм в несколько шагов.

В этой статье мы подробно разберем, как правильно передать результаты выполнения одного запроса во второй запрос с помощью объекта СписокЗначений, рассмотрим подводные камни этого механизма и методы оптимизации производительности.

Пошаговый алгоритм решения задачи

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

Разберем весь процесс по шагам.

Шаг 1. Создание и выполнение первого запроса

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


ЗапросКлиенты = СоздатьОбъект("Запрос");
ТекстЗапросаКлиенты = 
"//{{ЗАПРОС(ВыбратьКлиентов)
|Период с ВыбНачПериода по ВыбКонПериода; 
|Клиент = Документ.РасходнаяНакладная.Клиент;   
|Товар = Документ.РасходнаяНакладная.Товар;
|Группировка Клиент без групп;
|Условие(Товар в ВыбТовары);
|"//}}ЗАПРОС
;

Если ЗапросКлиенты.Выполнить(ТекстЗапросаКлиенты) = 0 Тогда
    Предупреждение("Ошибка выполнения запроса по поиску клиентов!");
    Возврат;
КонецЕсли;

Шаг 2. Выгрузка результатов в ТаблицуЗначений и СписокЗначений

Обратите внимание на частую ошибку начинающих разработчиков: попытку напрямую добавить значение из выборки запроса с помощью конструкции СписокКлиентов.ДобавитьЗначение(Запрос.Клиент) в процессе обхода группировок. Платформа выдаст ошибку Значение не представляет агрегатный объект, если обращение к полям запроса происходит некорректно или выборка не спозиционирована.

Чтобы избежать подобных ошибок и ускорить работу системы, мы применим наиболее эффективный и лаконичный способ — выгрузку результатов запроса в объект ТаблицаЗначений, а затем перенос нужной колонки в СписокЗначений. Рассмотрим, как это выглядит в коде:


// Создаем необходимые объекты
ТаблРезультат = СоздатьОбъект("ТаблицаЗначений");
СписокКлиентов = СоздатьОбъект("СписокЗначений");

// Выгружаем результаты запроса в таблицу значений
// Параметры метода Выгрузить: (ТаблицаЗначений, ФлагГруппировок, ФлагФункций)
ЗапросКлиенты.Выгрузить(ТаблРезультат, 0, 0);

// Переносим колонку "Клиент" из таблицы значений прямо в список значений
// Синтаксис метода Выгрузить для ТаблицыЗначений: Табл.Выгрузить(Список, НачСтрока, КонСтрока, ИмяКолонки)
ТаблРезультат.Выгрузить(СписокКлиентов, , , "Клиент");

Шаг 3. Использование СпискаЗначений во втором запросе

Теперь у нас есть заполненный объект СписокЗначений, содержащий ссылки на нужных контрагентов. Мы можем использовать его в предложении Условие(...) второго запроса, который выберет все расходные накладные по этим клиентам за указанный период:


ЗапросДокументы = СоздатьОбъект("Запрос");
ТекстЗапросаДокументы = 
"//{{ЗАПРОС(ВыбратьДокументы)
|Период с ВыбНачПериода по ВыбКонПериода; 
|Клиент = Документ.РасходнаяНакладная.Клиент;   
|Док = Документ.РасходнаяНакладная.ТекущийДокумент;
|Группировка Клиент без групп;
|Группировка Док;
|Условие(Клиент в СписокКлиентов);
|"//}}ЗАПРОС
;

Если ЗапросДокументы.Выполнить(ТекстЗапросаДокументы) = 0 Тогда
    Предупреждение("Ошибка выполнения запроса по документам!");
    Возврат;
КонецЕсли;

После этого мы можем стандартным образом обойти результаты второго запроса и сформировать необходимый отчет или обработать документы.

Важные нюансы и подводные камни

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

1. Ловушка пустого СпискаЗначений

Проанализируем крайне опасную особенность работы оператора В в условиях запросов 1С 7.7. Если ваш первый запрос не нашел ни одного клиента (например, за выбранный период никто не покупал указанный товар), то объект СписокКлиентов останется абсолютно пустым.

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

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


Если СписокКлиентов.РазмерСписка() = 0 Тогда
    // Если список пуст, выполнять второй запрос бессмысленно
    Предупреждение("За выбранный период продаж указанного товара не обнаружено.");
    Возврат;
КонецЕсли;

2. Обязательность агрегатных функций в запросах

В платформе 1С 7.7 так называемые "черные запросы" (запросы по документам, справочникам или регистрам) могут возвращать пустые или некорректные результаты, если в тексте запроса не используется хотя бы одна агрегатная функция (такая как Сумма(), Счетчик(), Расход()). Платформа оптимизирует выполнение запросов и ориентируется на наличие функций для корректного построения внутренних SQL-выборок.

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

|Функция КолДок = Счетчик();

3. Оптимизация производительности: переход на регистры

Делать запросы напрямую к документам (к их табличным частям и реквизитам шапки) — крайне неоптимальное архитектурное решение. В базах данных с большим объемом информации это гарантированно приведет к длительным зависаниям системы и перегрузке дисковой подсистемы.

Выясним причину: при обращении к документам система вынуждена сканировать физические таблицы журналов документов и табличных частей, совершая ресурсоемкие соединения на уровне СУБД. Гораздо правильнее использовать для этих целей оборотные регистры (например, регистр "Продажи" или "ПродажиТоваров"), которые специально спроектированы для быстрого получения итогов.

Рассмотрим пример оптимизированного первого запроса по регистру накопления:


|Период с ВыбНачПериода по ВыбКонПериода;
|Клиент = Регистр.Продажи.Покупатель;
|Товар = Регистр.Продажи.Номенклатура;
|СуммаПродажи = Регистр.Продажи.Сумма;
|Функция ОборотСумма = Расход(СуммаПродажи); // Обязательная функция для правильной работы регистра в запросе
|Группировка Клиент без групп;
|Условие(Товар в ВыбТовары);

Такой запрос отработает в десятки раз быстрее, особенно в крупных информационных базах.

Альтернативное решение: Прямые SQL-запросы (компонента 1С++)

Если ваша информационная база 1С 7.7 работает на SQL-версии (под управлением MS SQL Server), вы можете полностью отказаться от двухэтапного алгоритма с выгрузкой в списки значений. Штатные запросы 1С 7.7 транслируются платформой в неоптимальный T-SQL код.

Используя популярную внешнюю компоненту 1С++ и класс ODBCRecordset, вы можете написать один прямой SQL-запрос, который решит задачу в одно действие на стороне сервера базы данных с помощью соединения таблиц (JOIN).

Посмотрим на пример логики такого SQL-запроса:


SELECT 
    Журнал.IDDoc as [Док $Документ.РасходнаяНакладная],
    Шапка.SP123 as [Клиент $Справочник.Клиенты]
FROM 
    _1SJOURN as Журнал
INNER JOIN 
    $Документ.РасходнаяНакладная as Шапка ON Журнал.IDDoc = Шапка.IDDoc
WHERE 
    Шапка.SP123 IN (
        SELECT DISTINCT ВнутрШапка.SP123
        FROM $ДокументСтроки.РасходнаяНакладная as ТЧ
        INNER JOIN $Документ.РасходнаяНакладная as ВнутрШапка ON ТЧ.IDDoc = ВнутрШапка.IDDoc
        WHERE ТЧ.$ПределенныйТовар = :ВыбТовар
    )

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

← На главную