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

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При разработке отчетов и обработок в платформе 1С часто возникает типовая задача: даны две выборки или таблицы данных (назовем их ТаблицаА и ТаблицаБ), и требуется получить только те строки из ТаблицыА, которых нет в ТаблицеБ (операция разности множеств или вычитания таблиц).

В стандартном языке запросов SQL для этого существуют операторы EXCEPT (или MINUS) и предикат NOT EXISTS. Однако в языке запросов платформы 1С:Предприятие таких операторов нет. Разберем по шагам основные способы решения этой задачи, их преимущества, ограничения и правила оптимизации.

Способ 1. Использование конструкции ЛЕВОЕ СОЕДИНЕНИЕ и условия ЕСТЬ NULL (Рекомендуемый)

Проанализируем классический и наиболее производительный подход. Мы соединяем исходную ТаблицуА с исключаемой ТаблицейБ по ключевым полям с помощью левого соединения. Если строка присутствует в обеих таблицах, поля правой таблицы заполняются соответствующими значениями. Если же строки в ТаблицеБ нет, все ее поля в результирующей строке примут специальное значение NULL. Отфильтровав результат по условию ЕСТЬ NULL, мы получим только те записи, которые есть исключительно в ТаблицеА.

Посмотрим на пример текста запроса:


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

Разберем важные нюансы этого способа:

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

Способ 2. Использование предиката НЕ В с подзапросом

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

Посмотрим на пример запроса:


ВЫБРАТЬ
    А.Ссылка КАК Номенклатура
ИЗ
    Справочник.Номенклатура КАК А
ГДЕ
    А.Ссылка НЕ В
        (ВЫБРАТЬ РАЗЛИЧНЫЕ
            Б.Номенклатура
        ИЗ
            РегистрСведений.ЦеныНоменклатуры.СрезПоследних КАК Б
        ГДЕ
            Б.Номенклатура ЕСТЬ НЕ NULL)

Выясним особенности и подводные камни этого метода:

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

Способ 3. Использование ОБЪЕДИНИТЬ ВСЕ и группировки

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

Логика метода строится на добавлении маркера принадлежности к таблице и последующей группировке:

  1. Объединяем данные двух таблиц через ОБЪЕДИНИТЬ ВСЕ, добавив признак источника (например, 1 для первой таблицы и 0 для второй).
  2. Группируем результат по всем ключевым полям.
  3. В секции ИМЕЮЩИЕ отсекаем строки, присутствующие во второй таблице.

Посмотрим на структуру такого запроса:


ВЫБРАТЬ
    ОбъединенныеДанные.Номенклатура КАК Номенклатура,
    ОбъединенныеДанные.Характеристика КАК Характеристика
ИЗ
    (ВЫБРАТЬ
        А.Номенклатура КАК Номенклатура,
        А.Характеристика КАК Характеристика,
        0 КАК ПризнакТаблицыБ
    ИЗ
        ТаблицаА КАК А

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

    ВЫБРАТЬ
        Б.Номенклатура,
        Б.Характеристика,
        1 КАК ПризнакТаблицыБ
    ИЗ
        ТаблицаБ КАК Б) КАК ОбъединенныеДанные
СГРУППИРОВАТЬ ПО
    ОбъединенныеДанные.Номенклатура,
    ОбъединенныеДанные.Характеристика
ИМЕЮЩИЕ
    МАКСИМУМ(ОбъединенныеДанные.ПризнакТаблицыБ) = 0

Этот подход избавляет от необходимости писать сложные связки в секции ПО и хорошо показывает себя при пакетной обработке однотипных наборов данных.

← На главную