При разработке отчетов и обработок в 1С часто возникает задача получить список товаров или материалов, по которым не было движения (например, поступления) за выбранный интервал времени. Типовой подход заключается в левом соединении справочника Номенклатура с табличной частью документа поступления и последующей фильтрации по отсутствию записей с помощью проверки ЕСТЬ NULL.
Однако при попытке ограничить выборку документом за конкретный период разработчики часто сталкиваются со ситуацией, когда запрос возвращает пустой результат. Проанализируем, почему возникает эта ошибка и как корректно составить запрос языка 1С.
Рассмотрим неудачный вариант запроса, с которым столкнулся разработчик:
ВЫБРАТЬ
Ном.Ссылка,
ПоступлениеТоваровУслугТовары.Номенклатура
ИЗ
Справочник.Номенклатура КАК Ном
ЛЕВОЕ СОЕДИНЕНИЕ Документ.ПоступлениеТоваровУслуг.Товары КАК ПоступлениеТоваровУслугТовары
ПО (ПоступлениеТоваровУслугТовары.Номенклатура = Ном.Ссылка)
ГДЕ
Ном.ЭтоГруппа = ЛОЖЬ
И ПоступлениеТоваровУслугТовары.Номенклатура ЕСТЬ NULL
И ПоступлениеТоваровУслугТовары.Ссылка.Дата МЕЖДУ &Дата И &ДатаКон
Разберем по шагам, что происходит при выполнении этого запроса СУБД:
ЛЕВОЕ СОЕДИНЕНИЕ таблицы справочника Номенклатура с табличной частью Документ.ПоступлениеТоваровУслуг.Товары. Для позиций номенклатуры, по которым поступлений не было, поля правой таблицы заполняются значениями NULL.ГДЕ для фильтрации полученной таблицы. Наложение условия ПоступлениеТоваровУслугТовары.Ссылка.Дата МЕЖДУ &Дата И &ДатаКон проверяет дату документа для каждой строки.ПоступлениеТоваровУслугТовары.Ссылка.Дата равно NULL. Результат сравнения NULL с диапазоном дат всегда дает ЛОЖЬ (неопределенность), поэтому СУБД отсекает абсолютно все строки с NULL.Таким образом, наложение условия на правую таблицу в секции ГДЕ фактически превращает ЛЕВОЕ СОЕДИНЕНИЕ во ВНУТРЕННЕЕ СОЕДИНЕНИЕ, вычеркивая именно те записи, которые мы пытались отобрать через ЕСТЬ NULL.
Самое простое и быстрое исправление — перенести фильтрацию по дате документа из секции ГДЕ в условие соединения ПО правой таблицы. В этом случае ограничение по периоду применяется до формирования левого соединения, и значения NULL для отсутствующих товаров сохраняются.
Посмотрим на пример правильного запроса:
ВЫБРАТЬ
Ном.Ссылка КАК Номенклатура
ИЗ
Справочник.Номенклатура КАК Ном
ЛЕВОЕ СОЕДИНЕНИЕ Документ.ПоступлениеТоваровУслуг.Товары КАК ПоступлениеТоваровУслугТовары
ПО (ПоступлениеТоваровУслугТовары.Номенклатура = Ном.Ссылка)
И (ПоступлениеТоваровУслугТовары.Ссылка.Дата МЕЖДУ &Дата И &ДатаКон)
ГДЕ
Ном.ЭтоГруппа = ЛОЖЬ
И ПоступлениеТоваровУслугТовары.Номенклатура ЕСТЬ NULL
В данном случае платформа сначала отберет строки табличной части за указанный период, а затем присоединит их к справочнику. Для элементов номенклатуры, не поступавших в этот период, поле ПоступлениеТоваровУслугТовары.Номенклатура станет NULL, и условие в ГДЕ отработает корректно.
Другим наглядным решением является отбор элементов номенклатуры с помощью конструкции ГДЕ НЕ Номенклатура В (...). Подзапрос собирает все товары, поступавшие за период, а главный запрос выбирает только те позиций номенклатуры, которые не входят в этот список.
Рассмотрим структуру такого запроса:
ВЫБРАТЬ
Ном.Ссылка КАК Номенклатура
ИЗ
Справочник.Номенклатура КАК Ном
ГДЕ
Ном.ЭтоГруппа = ЛОЖЬ
И НЕ Ном.Ссылка В
(ВЫБРАТЬ РАЗЛИЧНЫЕ
ТЧТовары.Номенклатура
ИЗ
Документ.ПоступлениеТоваровУслуг.Товары КАК ТЧТовары
ГДЕ
ТЧТовары.Ссылка.Дата МЕЖДУ &Дата И &ДатаКон)
Этот способ легко читается, но при больших объемах данных в базе 1С он может уступать по скорости выполнения оптимально построенным левым соединениям.
Если задача выполняется на крупной информационной базе, прямой запрос к табличным частям документов через точку (ПоступлениеТоваровУслугТовары.Ссылка.Дата) создает неявные соединения в SQL и может вызывать проблемы с производительностью. Кроме того, прямое чтение табличной части выбирает данные в том числе из непроведенных или помеченных на удаление документов.
С точки зрения стандартов разработки 1С наилучшим решением является использование пакетного запроса с временной таблицей и явной фильтрацией проведенных документов:
ВЫБРАТЬ РАЗЛИЧНЫЕ
ТЧТовары.Номенклатура КАК Номенклатура
ПОМЕСТИТЬ ВТ_ПоступившиеТовары
ИЗ
Документ.ПоступлениеТоваровУслуг.Товары КАК ТЧТовары
ГДЕ
ТЧТовары.Ссылка.Дата МЕЖДУ &Дата И &ДатаКон
И ТЧТовары.Ссылка.Проведен = ИСТИНА;
ИНОСИРИРОВАТЬ ВТ_ПоступившиеТовары ПО Номенклатура;
ВЫБРАТЬ
Ном.Ссылка КАК Номенклатура
ИЗ
Справочник.Номенклатура КАК Ном
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ПоступившиеТовары КАК ВТ_ПоступившиеТовары
ПО (ВТ_ПоступившиеТовары.Номенклатура = Ном.Ссылка)
ГДЕ
Ном.ЭтоГруппа = ЛОЖЬ
И ВТ_ПоступившиеТовары.Номенклатура ЕСТЬ NULL
Преимущества данного подхода:
Номенклатура существенно ускоряет операцию левого соединения СУБД.Проведен = ИСТИНА гарантирует, что черновики и распроведенные документы не исказят отчет.В большинстве конфигураций 1С (таких как ERP, Комплексная автоматизация, Управление торговлей или Бухгалтерия) при проведении документов поступления данные записываются в регистры накопления (например, Закупки или ТоварыНаСкладах).
Запрос к виртуальной таблице Обороты регистра накопления работает значительно быстрее, чем обращение к документам, так как использует готовые агрегаты базы данных:
ВЫБРАТЬ
Ном.Ссылка КАК Номенклатура
ИЗ
Справочник.Номенклатура КАК Ном
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.Закупки.Обороты(&ДатаНач, &ДатаКон, Авто, ) КАК ЗакупкиОбороты
ПО (ЗакупкиОбороты.Номенклатура = Ном.Ссылка)
ГДЕ
Ном.ЭтоГруппа = ЛОЖЬ
И ЗакупкиОбороты.Номенклатура ЕСТЬ NULL
Подводя итог: при необходимости отфильтровать отсутствующие данные всегда проверяйте, куда именно помещено условие по правой таблице. Для левого соединения ограничения правой таблицы должны находиться либо в секции ПО, либо во временной таблице подзапроса.