Как в запросе 1С отобрать заказы покупателей без введенных реализаций?

Программист 1С v8.3 (Обычные формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

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

Базовый принцип: Левое соединение и проверка на ЕСТЬ NULL

Классический подход к поиску отсутствующих подчиненных документов заключается в связывании исходной таблицы документов с подчиненной через левое внешнее соединение (ЛЕВОЕ СОЕДИНЕНИЕ). В секции условий ГДЕ при этом выполняется отбор записей, для которых поле присоединенной таблицы равно NULL.

Посмотрим на базовый пример запроса:


ВЫБРАТЬ
    Заказ.Ссылка КАК Ссылка
ИЗ
    Документ.ЗаказПокупателя КАК Заказ
        ЛЕВОЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг КАК Реализация
        ПО Реализация.Сделка = Заказ.Ссылка
ГДЕ
    Реализация.Ссылка ЕСТЬ NULL

В этом примере платформа выбирает все заказы покупателей и присоединяет к ним реализации, у которых поле Сделка ссылается на соответствующий заказ. Для заказов, по которым реализации не создавались, поля правой таблицы заполняются значением NULL. Условие Реализация.Ссылка ЕСТЬ NULL отсекает все заказы, имеющие хотя бы одну подчиненную накладную.

Сложные связи: поиск подчиненных документов по табличным частям

В типовых конфигурациях (например, в Управлении торговлей ред. 10.3 или Комплексной автоматизации) связь между заказом и реализацией может храниться не только в реквизите шапки (Сделка или ДокументОснование), но и построчно в табличных частях: Товары, Услуги, ВозвратнаяТара или ДокументыРасчетовСКонтрагентом.

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

Разберем по шагам построение комплексного пакетного запроса:

  1. Формирование списка исходных заказов. Отбираем заказы за указанный период. Если используется обращение к виртуальной таблице регистра, обязательно передаем параметры периода и фильтрации прямо в параметры таблицы.
  2. Сбор подчиненных документов. Объединяем ссылки на заказы из шапки и табличных частей документов реализации с использованием оператора ОБЪЕДИНИТЬ ВСЕ.
  3. Итоговая фильтрация. Связываем исходные заказы со списком найденных подчиненных документов и оставляем только строки без связей.

Рассмотрим пример реализации такого запроса:


ВЫБРАТЬ РАЗЛИЧНЫЕ
    ЗаказыПокупателейОбороты.ЗаказПокупателя КАК Ссылка
ПОМЕСТИТЬ ВТ_Заказы
ИЗ
    РегистрНакопления.ЗаказыПокупателей.Обороты(&НачДата, &КонДата, Регистратор, Регистратор ССЫЛКА Документ.ЗаказПокупателя) КАК ЗаказыПокупателейОбороты
ИНДЕКСИРОВАТЬ ПО
    Ссылка
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ РАЗЛИЧНЫЕ
    Подчиненные.ДокументОснование КАК ДокументОснование
ПОМЕСТИТЬ ВТ_ЗаказыСРеализациями
ИЗ
    (ВЫБРАТЬ
        РеализацияТоваровУслуг.Сделка КАК ДокументОснование
    ИЗ
        Документ.РеализацияТоваровУслуг КАК РеализацияТоваровУслуг
    ГДЕ
        РеализацияТоваровУслуг.Сделка В
            (ВЫБРАТЬ ВТ_Заказы.Ссылка ИЗ ВТ_Заказы)
        И РеализацияТоваровУслуг.Проведен = ИСТИНА

    ОБЪЕДИНИТЬ ВСЕ

    ВЫБРАТЬ
        РеализацияТовары.ЗаказПокупателя
    ИЗ
        Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТовары
    ГДЕ
        РеализацияТовары.ЗаказПокупателя В
            (ВЫБРАТЬ ВТ_Заказы.Ссылка ИЗ ВТ_Заказы)
        И РеализацияТовары.Ссылка.Проведен = ИСТИНА

    ОБЪЕДИНИТЬ ВСЕ

    ВЫБРАТЬ
        РеализацияУслуги.ЗаказПокупателя
    ИЗ
        Документ.РеализацияТоваровУслуг.Услуги КАК РеализацияУслуги
    ГДЕ
        РеализацияУслуги.ЗаказПокупателя В
            (ВЫБРАТЬ ВТ_Заказы.Ссылка ИЗ ВТ_Заказы)
        И РеализацияУслуги.Ссылка.Проведен = ИСТИНА) КАК Подчиненные
ИНДЕКСИРОВАТЬ ПО
    ДокументОснование
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ_Заказы.Ссылка КАК ЗаказПокупателя
ИЗ
    ВТ_Заказы КАК ВТ_Заказы
        ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ЗаказыСРеализациями КАК ВТ_ЗаказыСРеализациями
        ПО ВТ_Заказы.Ссылка = ВТ_ЗаказыСРеализациями.ДокументОснование
ГДЕ
    ВТ_ЗаказыСРеализациями.ДокументОснование ЕСТЬ NULL

Типичные ошибки при написании условий на отсутствие подчиненных

Выясним причину частых логических ошибок, с которыми сталкиваются разработчики при написании подобных запросов:

  1. Взаимоисключающие условия в секции ГДЕ. Конструкция вида ГДЕ ВТ_ПодчиненныеДокументы.Ссылка <> 0 И ВТ_ПодчиненныеДокументы.Ссылка ЕСТЬ NULL никогда не вернет данные. Если поле равно NULL, любое сравнение с конкретным значением (даже неравенство <>) возвращает ЛОЖЬ. Для отсечения достаточно оставить исключительно ЕСТЬ NULL.
  2. Подмена NULL через ЕСТЬNULL в условиях соединения. Использование функций вроде ЕСТЬNULL(Поле, 0) в списке выборки допустимо, однако проверка в секции ГДЕ должна адресоваться к исходному полю таблицы, по которому строилось соединение.
  3. Игнорирование статуса проведения документов. По умолчанию в выборку документов реализации попадают даже помеченные на удаление или непроведенные документы. С точки зрения учета заказ без проведенной отгрузки считается невыполненным. Всегда проверяйте статус: Реализация.Проведен = ИСТИНА и Реализация.ПометкаУдаления = ЛОЖЬ.
  4. Параметры виртуальных таблиц. Наложение условий по датам в секции ГДЕ после вызова таблицы Обороты заставляет СУБД считывать данные за всю историю базы и только потом отсекать лишнее. Передавайте границы периода &НачДата и &КонДата строго в первые два параметра виртуальной таблицы.
  5. Оператор ОБЪЕДИНИТЬ вместо ОБЪЕДИНИТЬ ВСЕ. Использование ОБЪЕДИНИТЬ без ключевого слова ВСЕ запускает ресурсоемкую операцию поиска дубликатов на каждом шаге склейки таблиц. Оптимальнее использовать ОБЪЕДИНИТЬ ВСЕ, а оператор РАЗЛИЧНЫЕ применить один раз на выходе.

Альтернативный вариант: конструкция ГДЕ НЕ ... В

В некоторых случаях лаконичнее и быстрее использовать проверку через отрицание вхождения. СУБД трансформирует такие запросы в эффективное анти-соединение (anti-join):


ВЫБРАТЬ
    ВТ_Заказы.Ссылка КАК ЗаказПокупателя
ИЗ
    ВТ_Заказы КАК ВТ_Заказы
ГДЕ
    НЕ ВТ_Заказы.Ссылка В
        (ВЫБРАТЬ
            ВТ_ЗаказыСРеализациями.ДокументОснование
        ИЗ
            ВТ_ЗаказыСРеализациями КАК ВТ_ЗаказыСРеализациями)

Такой синтаксис исключает необходимость наложения внешних соединений в финальном запросе и снижает риск допустить ошибку при фильтрации по NULL.

← На главную