Как в запросе 1С 7.7 связать регистр со справочником и отобрать пустые ячейки?

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

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

Почему не работает прямое условие Количество = 0

Проанализируем типичную ошибку, возникающую при попытке отобрать нулевые остатки в штатном запросе платформы 7.7:


|Ячейка = Регистр.ЯчейкиСтеллажей.Ячейка;
|Количество = Регистр.ЯчейкиСтеллажей.Количество;
|Функция КоличествоКонОст = КонОст(Количество);
|Группировка Ячейка;
|Условие (Количество = 0);

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

  1. Оператор Условие накладывается на исходные записи физических таблиц еще до расчета агрегатных функций (до вычисления итогов).
  2. В таблицах движений и итогов регистров остатков физически хранятся только ненулевые значения. Записей с нулевым количеством там попросту нет.
  3. Попытка фильтрации по агрегатной функции вида Условие (КоличествоКонОст = 0) в 1С 7.7 синтаксически недопустима.

Таким образом, условие Количество = 0 полностью исключает выборку строк из регистра.

Решение 1. Использование составного описания поля и модификатора ВСЕ

Штатный механизм запросов 1С 7.7 позволяет сымитировать левое соединение (LEFT JOIN) справочника с регистром. Для этого в описании переменной запроса через запятую перечисляются поле регистра и ссылка на справочник, а в группировке указывается ключевое слово ВСЕ.

Посмотрим на пример правильного построения запроса:


Запрос = СоздатьОбъект("Запрос");
ТекстЗапроса = "
|Ячейка = Регистр.ЯчейкиСтеллажей.Ячейка, Справочник.ЯчейкиСклада.ТекущийЭлемент;
|Количество = Регистр.ЯчейкиСтеллажей.Количество;
|Функция КоличествоКонОст = КонОст(Количество);
|Группировка Ячейка упорядочить по Ячейка.СтоимостьВГрафе ВСЕ;
|Условие (Ячейка.ЭтоГруппа() = 0);
|Условие (Ячейка.ПометкаУдаления() = 0);
|Условие (Ячейка.МаксВес >= _весТовара);
|Без итогов;
|";

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

Рассмотрим подробнее важные детали этой конструкции:

  1. Связка источников: строка Ячейка = Регистр.ЯчейкиСтеллажей.Ячейка, Справочник.ЯчейкиСклада.ТекущийЭлемент; указывает запросу сопоставлять измерение регистра со всеми элементами справочника.
  2. Модификатор ВСЕ: ключевое слово ВСЕ в строке Группировка Ячейка ... ВСЕ; заставляет генератор запроса включить в результат абсолютно все элементы справочника, даже если по ним нет никаких движений и остатков в регистре.
  3. Проверка групп и пометок на удаление: поскольку модификатор ВСЕ поднимает всю таблицу справочника, в результат попадут папки (в иерархических справочниках) и помеченные на удаление элементы. Фильтры Условие (Ячейка.ЭтоГруппа() = 0); и Условие (Ячейка.ПометкаУдаления() = 0); обязательны.
  4. Подчиненные справочники: если справочник ячеек подчинен справочнику складов, необходимо обязательно добавить условие по владельцу: Условие (Ячейка.Владелец = ВыбСклад);, иначе запрос выберет ячейки по всем складам компании.
  5. Модификатор Без итогов: директива Без итогов; отключает расчет общих итогов по всему запросу, что существенно ускоряет выборку данных.
  6. Фильтрация остатка: так как в тексте запроса нельзя отсечь нули, проверка Если Запрос.КоличествоКонОст = 0 Тогда выполняется вручную внутри цикла обхода группировки.

Решение 2. Обход справочника с проверкой через ВыгрузитьИтоги

Штатный запрос с модификатором ВСЕ на больших объемах данных (десятки тысяч ячеек) в файловых базах DBF может работать медленно из-за полного перебора физических таблиц. В таких ситуациях гораздо быстрее и надежнее отработать задачу в два этапа: выгрузить ненулевые остатки в таблицу значений и пробежаться по справочнику штатными методами платформы.

Разберем данный алгоритм на практике:


РегОстатки = СоздатьОбъект("Регистр.ЯчейкиСтеллажей");
ТЗ_Остатки = СоздатьОбъект("ТаблицаЗначений");

// Выгружаем только фактически занятые ячейки в ТаблицуЗначений
РегОстатки.ВыгрузитьИтоги(ТЗ_Остатки, 1, 1);

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

СпрЯчейки.ВыбратьЭлементы();
Пока СпрЯчейки.ПолучитьЭлемент() = 1 Цикл
    // Пропускаем группы и помеченные на удаление элементы
    Если СпрЯчейки.ЭтоГруппа() = 1 Тогда
        Продолжить;
    КонецЕсли;
    Если СпрЯчейки.ПометкаУдаления() = 1 Тогда
        Продолжить;
    КонецЕсли;

    // Проверяем реквизиты элемента
    Если СпрЯчейки.МаксВес < _весТовара Тогда
        Продолжить;
    КонецЕсли;

    // Ищем ячейку в таблице остатков
    СтрокаТЗ = 0;
    Если ТЗ_Остатки.НайтиЗначение(СпрЯчейки.ТекущийЭлемент(), СтрокаТЗ, "Ячейка") = 0 Тогда
        // Ячейка отсутствует в регистре — значит, она полностью пустая
        Сообщить("Найдена свободная ячейка: " + СпрЯчейки.ТекущийЭлемент());
    КонецЕсли;
КонецЦикла;

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

Решение 3. Использование прямого SQL-запроса через 1С++

Если информационная база работает под управлением Microsoft SQL Server (или при использовании внешней компоненты 1С++ для DBF-версий), самым производительным решением будет написание прямого запроса. Это позволяет использовать классический синтаксис LEFT JOIN и отсекать занятые ячейки на уровне СУБД с задействованием индексов.

Посмотрим на пример запроса через класс ODBCRecordset компоненты 1С++:


ЗапросSQL = СоздатьОбъект("ODBCRecordset");
ТекстSQL = "
|SELECT 
|    Спр.ID as [Ячейка $Справочник.ЯчейкиСклада]
|FROM 
|    $Справочник.ЯчейкиСклада AS Спр
|LEFT JOIN 
|    $РегистрОстатки.ЯчейкиСтеллажей(:ВыбДата~,,
|                                    ,(Ячейка),
|                                    (Количество)) AS Рег ON Рег.Ячейка = Спр.ID
|WHERE 
|    Спр.ISFOLDER = 2
|    AND Спр.ISMARK = 0
|    AND $Спр.МаксВес >= :ВыбВес
|    AND $Спр.Владелец = :ВыбСклад
|    AND Рег.Количество IS NULL
|ORDER BY 
|    $Спр.СтоимостьВГрафе
|";

ЗапросSQL.УстановитьТекстовыйПараметр("ВыбДата", ТекущаяДата());
ЗапросSQL.УстановитьТекстовыйПараметр("ВыбВес", _весТовара);
ЗапросSQL.УстановитьТекстовыйПараметр("ВыбСклад", ВыбСклад);

ТЗРезультат = ЗапросSQL.ВыполнитьИнструкцию(ТекстSQL);

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

← На главную