К статье
Практический материал

Разработка и Конфигурация · 12 мин

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

Как сравнить две таблицы значений в 1С через запрос: помещение во временные таблицы, полное соединение, поиск различий и совпадений. Примеры кода и типичные ошибки.

Задача сравнить две таблицы значений возникает постоянно: сверить данные до и после обработки, найти расхождения между документом и регистром, определить, какие строки добавились или исчезли. Сравнение в цикле «для каждой строки ищем в другой таблице» работает медленно и порождает громоздкий код. Запрос решает ту же задачу декларативно и быстро, потому что вся работа выполняется на сервере СУБД.

Основной приём — поместить обе таблицы значений во временные таблицы менеджера временных таблиц, а затем объединить их полным соединением (FULL JOIN) или левым соединением с проверкой на NULL. Результат — выборка, где видно, какие строки совпадают, какие есть только в первой таблице и какие только во второй.

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

Почему запрос эффективнее сравнения в цикле

Классический подход «в лоб» — вложенные циклы: для каждой строки первой таблицы вызывается Найти() или НайтиСтроки() по второй. На таблицах в несколько тысяч строк это даёт квадратичную сложность: десять тысяч строк против десяти тысяч — это до ста миллионов операций сравнения, и обработка занимает минуты.

Запрос переносит сравнение на уровень СУБД, где соединение выполняется оптимизированными алгоритмами (хеш-соединение, соединение слиянием). Те же десять тысяч строк сравниваются за доли секунды. Дополнительный плюс — код становится короче и читается как декларативное описание: «покажи строки, которых нет в другой таблице».

Запрос особенно оправдан, когда результат сравнения нужен для дальнейшей выборки из базы: например, найти расхождения и сразу дочитать по ним данные из регистров. Всё это делается одним пакетным запросом без лишних обращений к серверу.

Подготовка: помещение таблиц значений во временные таблицы

Чтобы таблица значений стала доступна в запросе, её нужно поместить во временную таблицу. Для этого в тексте запроса используется конструкция ВЫБРАТЬ ... ПОМЕСТИТЬ ИмяВременнойТаблицы ИЗ &Таблица КАК Таблица, а сама таблица передаётся как параметр запроса.

Обе таблицы можно поместить в одном пакетном запросе, разделив части точкой с запятой. Обязательное условие — у запроса должен быть назначен менеджер временных таблиц, иначе временные таблицы будут уничтожены сразу после выполнения пакета.

МенеджерВТ = Новый МенеджерВременныхТаблиц;

Запрос = Новый Запрос;

Запрос.МенеджерВременныхТаблиц = МенеджерВТ;

Запрос.Текст =

"ВЫБРАТЬ

| Т.Номенклатура КАК Номенклатура,

| Т.Количество КАК Количество

|ПОМЕСТИТЬ ТаблицаДо

|ИЗ &ТаблицаДо КАК Т

|;

|ВЫБРАТЬ

| Т.Номенклатура КАК Номенклатура,

| Т.Количество КАК Количество

|ПОМЕСТИТЬ ТаблицаПосле

|ИЗ &ТаблицаПосле КАК Т";

Запрос.УстановитьПараметр("ТаблицаДо", ТЗДо);

Запрос.УстановитьПараметр("ТаблицаПосле", ТЗПосле);

Запрос.Выполнить();

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

Основной запрос: полное соединение и поиск различий

Полное соединение (FULL JOIN) возвращает все строки обеих таблиц. Там, где пары не нашлось, поля противоположной стороны равны NULL. Именно по этому признаку определяются различия.

ЗапросСравнения = Новый Запрос;

ЗапросСравнения.МенеджерВременныхТаблиц = МенеджерВТ;

ЗапросСравнения.Текст =

"ВЫБРАТЬ

| ЕСТЬNULL(До.Номенклатура, После.Номенклатура) КАК Номенклатура,

| До.Количество КАК КоличествоДо,

| После.Количество КАК КоличествоПосле,

| ВЫБОР

| КОГДА До.Номенклатура ЕСТЬ NULL

| ТОГДА ""Добавлена""

| КОГДА После.Номенклатура ЕСТЬ NULL

| ТОГДА ""Удалена""

| КОГДА До.Количество <> После.Количество

| ТОГДА ""Изменена""

| ИНАЧЕ ""Совпадает""

| КОНЕЦ КАК Статус

|ИЗ ТаблицаДо КАК До

| ПОЛНОЕ СОЕДИНЕНИЕ ТаблицаПосле КАК После

| ПО До.Номенклатура = После.Номенклатура";

Результат = ЗапросСравнения.Выполнить().Выгрузить();

В колонке Статус каждая строка получает одну из четырёх меток. Если нужны только расхождения, добавьте в запрос условие ГДЕ До.Номенклатура ЕСТЬ NULL ИЛИ После.Номенклатура ЕСТЬ NULL ИЛИ До.Количество <> После.Количество — тогда совпадающие строки в результат не попадут.

Ключ соединения не обязан быть одной колонкой. Если строки идентифицируются комбинацией «Номенклатура + Склад + Серия», перечислите все поля в условии соединения через И. От состава ключа напрямую зависит корректность результата.

bsl
Запрос.Текст = "ВЫБРАТЬ До.Номенклатура ИЗ ТаблицаДо КАК До ЛЕВОЕ СОЕДИНЕНИЕ ТаблицаПосле КАК После ПО До.Номенклатура = После.Номенклатура ГДЕ После.Номенклатура ЕСТЬ NULL";

Варианты сравнения под разные задачи

Найти строки, которых нет во второй таблице

Если нужен односторонний результат — например, «что удалили из таблицы» — достаточно левого соединения с фильтром по NULL. Это дешевле полного соединения и проще читается.

"ВЫБРАТЬ До.Номенклатура

|ИЗ ТаблицаДо КАК До

| ЛЕВОЕ СОЕДИНЕНИЕ ТаблицаПосле КАК После

| ПО До.Номенклатура = После.Номенклатура

|ГДЕ После.Номенклатура ЕСТЬ NULL"

Найти только совпадающие строки

Для пересечения таблиц используйте внутреннее соединение (ВНУТРЕННЕЕ СОЕДИНЕНИЕ). В результат попадут только строки, ключ которых присутствует в обеих таблицах.

Сравнение без учёта порядка строк и дублей

Запрос сравнивает множества по ключу, а не позиции строк — порядок в исходных таблицах значений роли не играет. Но если в одной таблице ключ повторяется, соединение перемножит строки, и вы получите дубли в результате. Перед сравнением сверните таблицы: либо методом Свернуть() в коде, либо группировкой СГРУППИРОВАТЬ ПО с агрегатными функциями прямо в запросе помещения во временную таблицу.

Сравнение вариантов

Полное соединение

Подходит для полной сверки двух таблиц: видно добавленные, удалённые и изменённые строки. Ограничение — требует аккуратной обработки NULL в полях обеих сторон.

Левое соединение с ЕСТЬ NULL

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

Внутреннее соединение

Подходит для поиска пересечения таблиц. Не показывает различия — только совпадения.

Цикл с НайтиСтроки()

Подходит только для маленьких таблиц и простых проверок без обращения к СУБД. На больших объёмах работает на порядки медленнее запроса.

Требования к колонкам: типы, имена и состав

Платформа строго относится к структуре таблицы значений, передаваемой параметром запроса. Нарушение требований — самая частая причина ошибок вида «Неверные параметры в операции сравнения» или «Поле не найдено».

Основные правила:

  • Колонки, участвующие в соединении, должны иметь совместимые типы в обеих таблицах. Если в одной таблице колонка имеет составной тип (например, справочник «Номенклатура» и строка), а в другой — чистый тип, сравнение может дать неожиданный результат или ошибку.
  • Имена колонок в таблицах значений могут различаться — вы сами задаёте псевдонимы в запросе. Но внутри одной таблицы имена должны быть уникальны и соответствовать правилам имён переменных.
  • Колонки типа «ХранилищеЗначения» нельзя использовать в соединениях и сравнениях — исключите их из выборки во временную таблицу.
  • Числовые колонки с разной разрядностью сравниваются корректно, но строки с разной длиной лучше привести к одному виду через ВЫРАЗИТЬ.

Если типы колонок-ключей различаются, приведите их явно в запросе помещения: ВЫРАЗИТЬ(Т.Код КАК СТРОКА(20)) КАК Код. Это снимает неоднозначность ещё до соединения.

Типичные ошибки
01
Не назначен МенеджерВременныхТаблиц у второго запроса — временные таблицы не найдены, запрос падает с ошибкой «Таблица не найдена».
02
Соединение по колонке с дублями — результат перемножается, и вместо десяти расхождений выводится сто строк.
03
Сравнение NULL обычным равенством в коде — NULL никогда не равно NULL, поэтому проверки делаются только через ЕСТЬ NULL или ЕСТЬNULL().
04
Разные типы ключевых колонок в двух таблицах — соединение молча не находит совпадений, и вся выборка помечается как различия.
05
Передача в запрос таблицы с колонкой типа ХранилищеЗначения — ошибка выполнения запроса.

Проверка результата и контрольные точки

После выполнения сравнения полезно убедиться, что результат согласуется с исходными данными. Простая арифметическая проверка: количество строк со статусом «Совпадает» плюс «Удалена» должно равняться числу строк первой таблицы (при отсутствии дублей по ключу), а «Совпадает» плюс «Добавлена» — числу строк второй.

Для отладки выгрузите результат в табличный документ или посмотрите выборку в консоли запросов. Если все строки неожиданно помечены как различия — почти наверняка проблема в типах ключевых колонок или в лишних пробелах в строковых ключах.

Контрольные точки

Как проверить результат

Сумма статусов
Норма

Совпадает + Удалена = числу строк первой таблицы; Совпадает + Добавлена = числу строк второй

Тревожный признак

Суммы не сходятся — вероятны дубли ключей или ошибка в условии соединения

Доля совпадений
Норма

Часть строк имеет статус «Совпадает» при заведомо похожих данных

Тревожный признак

Все строки различны при одинаковых данных — проверяйте типы и пробелы в ключевых колонках

Количество строк результата
Норма

Не превышает суммы строк обеих таблиц

Тревожный признак

Строк больше, чем сумма исходных — ключ соединения не уникален

Альтернативы запросу и границы применимости

Запрос не всегда обязателен. Для таблиц до нескольких сотен строк сравнение в коде через индексированный поиск (НайтиСтроки с предварительно построенным соответствием) вполне приемлемо и не требует обращения к СУБД. Ещё один вариант — метод Свернуть() с последующим объединением таблиц и группировкой: добавить в каждую таблицу служебную колонку-флаг, скопировать строки в одну общую таблицу и свернуть по ключу — расхождения видны по сумме флагов.

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

Маршрут решения

Как выбрать действие

Таблицы небольшие (до нескольких сотен строк) и база данных не нужнаИспользуйте сравнение в коде через соответствие — проще и без лишнего обращения к СУБД
Таблицы большие или результат нужно соединить с данными регистровПомещайте обе таблицы во временные таблицы и сравнивайте полным соединением
Нужна только односторонняя проверка «чего не хватает»Используйте левое соединение с фильтром ЕСТЬ NULL
В таблицах возможны дубли по ключуСначала сверните или сгруппируйте данные, потом выполняйте соединение
Итог

Что важно запомнить

Сравнение двух таблиц значений запросом строится на трёх шагах: поместить обе таблицы во временные таблицы через общий менеджер, выполнить полное соединение по ключу, классифицировать строки по признаку NULL. Следите за типами колонок и дублями ключей — это главные источники ошибок.

Частые вопросы

Можно ли сравнить таблицы значений без помещения во временные таблицы?

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

Почему запрос не находит совпадений, хотя значения визуально одинаковые?

Чаще всего причина в разных типах колонок (число против строки), лишних пробелах или неразрывных пробелах в строковых ключах, либо в различии ссылок разных справочников. Приведите ключи к одному типу через ВЫРАЗИТЬ и проверьте строки функцией СокрЛП при заполнении таблиц.

Как сравнить таблицы по нескольким колонкам одновременно?

Перечислите все ключевые колонки в условии соединения через И: ПО До.Номенклатура = После.Номенклатура И До.Склад = После.Склад. Для сравнения остальных (неключевых) колонок добавьте проверки в секцию ВЫБОР или в условие ГДЕ.

Что делать, если в таблицах есть строки с пустыми значениями ключа?

Пустые ссылки и NULL ведут себя по-разному: NULL не равен ничему, включая другой NULL, поэтому такие строки не соединятся. Замените возможные NULL на пустые ссылки через ЕСТЬNULL ещё на этапе помещения во временную таблицу или добавьте в условие соединения обработку NULL.

Есть ли ограничение на размер таблицы значений, передаваемой в запрос?

Жёсткого ограничения платформа не задаёт, но практический предел определяется памятью сервера и длиной пакета запроса. Таблицы в десятки тысяч строк обычно обрабатываются без проблем; при сотнях тысяч строк разумнее сравнивать порциями или перенести данные в регистр сведений.

Можно ли сравнить таблицу значений с данными регистра напрямую?

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