При разработке отчетов и обработок в платформе 1С часто возникает типовая задача: даны две выборки или таблицы данных (назовем их ТаблицаА и ТаблицаБ), и требуется получить только те строки из ТаблицыА, которых нет в ТаблицеБ (операция разности множеств или вычитания таблиц).
В стандартном языке запросов SQL для этого существуют операторы EXCEPT (или MINUS) и предикат NOT EXISTS. Однако в языке запросов платформы 1С:Предприятие таких операторов нет. Разберем по шагам основные способы решения этой задачи, их преимущества, ограничения и правила оптимизации.
Проанализируем классический и наиболее производительный подход. Мы соединяем исходную ТаблицуА с исключаемой ТаблицейБ по ключевым полям с помощью левого соединения. Если строка присутствует в обеих таблицах, поля правой таблицы заполняются соответствующими значениями. Если же строки в ТаблицеБ нет, все ее поля в результирующей строке примут специальное значение NULL. Отфильтровав результат по условию ЕСТЬ NULL, мы получим только те записи, которые есть исключительно в ТаблицеА.
Посмотрим на пример текста запроса:
ВЫБРАТЬ
А.Ссылка КАК Номенклатура
ИЗ
Справочник.Номенклатура КАК А
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних КАК Б
ПО А.Ссылка = Б.Номенклатура
ГДЕ
Б.Номенклатура ЕСТЬ NULL
Разберем важные нюансы этого способа:
ГДЕ необходимо проверять поле таблицы Б, которое гарантированно заполнено в исходных данных (например, ключевое поле связи, поле ссылки или реквизит, не допускающий пустых значений). Если проверить поле, которое в самой таблице Б может содержать NULL, запрос вернет некорректные данные.ПО обязательно должны быть проиндексированы с помощью директивы ИНДЕКСИРОВАТЬ ПО. Отсутствие индексов на больших объемах данных приводит к резкому замедлению выполнения запроса.И в блоке ПО (например, по номенклатуре и характеристике одновременно).Рассмотрим альтернативный вариант с использованием вложенного запроса и конструкции НЕ В. В этом случае мы выбираем данные из первой таблицы, исключая те элементы, которые входят в выборку из второй таблицы.
Посмотрим на пример запроса:
ВЫБРАТЬ
А.Ссылка КАК Номенклатура
ИЗ
Справочник.Номенклатура КАК А
ГДЕ
А.Ссылка НЕ В
(ВЫБРАТЬ РАЗЛИЧНЫЕ
Б.Номенклатура
ИЗ
РегистрСведений.ЦеныНоменклатуры.СрезПоследних КАК Б
ГДЕ
Б.Номенклатура ЕСТЬ НЕ NULL)
Выясним особенности и подводные камни этого метода:
NULL, условие НЕ В вернет неопределенный результат, и весь запрос вернет пустую таблицу. Чтобы этого не произошло, во вложенном запросе обязательно нужно добавлять условие ГДЕ Б.Поле ЕСТЬ НЕ NULL.РАЗЛИЧНЫЕ, чтобы уменьшить объем передаваемых для сравнения данных.НЕ В в 1С не поддерживает кортежи (сравнение сразу по нескольким колонкам, например (Поле1, Поле2) НЕ В (...)). Если вычитание требуется выполнить по двум и более полям, данный метод не применим.Разберем еще один способ, который удобно использовать, когда требуется сравнить таблицы по большому количеству полей без написания громоздких условий соединения или когда требуется исключить повторяющиеся строки с учетом их точного количества.
Логика метода строится на добавлении маркера принадлежности к таблице и последующей группировке:
ОБЪЕДИНИТЬ ВСЕ, добавив признак источника (например, 1 для первой таблицы и 0 для второй).ИМЕЮЩИЕ отсекаем строки, присутствующие во второй таблице.Посмотрим на структуру такого запроса:
ВЫБРАТЬ
ОбъединенныеДанные.Номенклатура КАК Номенклатура,
ОбъединенныеДанные.Характеристика КАК Характеристика
ИЗ
(ВЫБРАТЬ
А.Номенклатура КАК Номенклатура,
А.Характеристика КАК Характеристика,
0 КАК ПризнакТаблицыБ
ИЗ
ТаблицаА КАК А
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
Б.Номенклатура,
Б.Характеристика,
1 КАК ПризнакТаблицыБ
ИЗ
ТаблицаБ КАК Б) КАК ОбъединенныеДанные
СГРУППИРОВАТЬ ПО
ОбъединенныеДанные.Номенклатура,
ОбъединенныеДанные.Характеристика
ИМЕЮЩИЕ
МАКСИМУМ(ОбъединенныеДанные.ПризнакТаблицыБ) = 0
Этот подход избавляет от необходимости писать сложные связки в секции ПО и хорошо показывает себя при пакетной обработке однотипных наборов данных.