Как правильно сравнить справочники, документы и регистры в 1С 8.3
Содержание
Сравнивать объекты в 1С нужно по ссылкам, а не по самим объектам: Документ1.Ссылка = Документ2.Ссылка даёт верный ответ, а сравнение двух экземпляров ДокументОбъект через = проверяет, один ли это экземпляр в памяти, а не одинаковые ли в нём данные. Содержимое (реквизиты, табличные части, движения) сравнивают отдельно: поэлементно в коде или запросом.
Частое заблуждение — что = в языке 1С «умеет» сравнивать структуры, массивы и таблицы значений. Не умеет: для коллекций оператор проверяет тождество, и две структуры с одинаковыми ключами и значениями окажутся не равны. Всё описанное ниже относится к платформе 8.3 и работает в любой типовой конфигурации; функции из БСП (ОбщегоНазначения) есть в БП 3.0, ЗУП 3.1, УТ 11 и других конфигурациях на её основе.
Как 1С сравнивает значения: операторы, ссылки и СравнениеЗначений
В языке 1С есть шесть операторов сравнения: =, <>, <, >, <=, >=. Логическое отрицание — НЕ. Поведение = зависит от типа значения.
| Что сравниваем | Как работает = | Чем пользоваться |
|---|---|---|
| Число, Строка, Дата, Булево | По значению | = напрямую |
| Ссылка (СправочникСсылка, ДокументСсылка) | По ссылке на запись в базе | = напрямую |
| Объект (СправочникОбъект, ДокументОбъект) | Тождество экземпляра | Сравнивать .Ссылка или реквизиты |
| Структура, Массив, Соответствие, ТаблицаЗначений | Тождество экземпляра | Поэлементно или ОбщегоНазначения.ДанныеСовпадают() |
| Неопределено и пустая ссылка | Это разные значения | ЗначениеЗаполнено() или .Пустая() |
Операторы < и > допустимы только для значений одного примитивного типа: Число, Строка, Дата, Булево. Попытка сравнить на «больше» ссылку со ссылкой или число со строкой вызывает ошибку времени выполнения.
Когда нужно упорядочить значения разных типов (например, при сортировке смешанной колонки), используйте объект СравнениеЗначений. Его метод Сравнить() возвращает −1, 0 или 1:
Сравнение = Новый СравнениеЗначений;
Результат = Сравнение.Сравнить(Значение1, Значение2);
Если Результат = 0 Тогда
Сообщить("Значения равны");
ИначеЕсли Результат < 0 Тогда
Сообщить("Первое значение меньше");
Иначе
Сообщить("Первое значение больше");
КонецЕсли;
Проверку типа делают отдельно, до сравнения значений:
Если ТипЗнч(Значение) = Тип("СправочникСсылка.Номенклатура") Тогда
// работаем с номенклатурой
КонецЕсли;
Для проверки «пусто или нет» не пишите Значение = Неопределено — пустая ссылка Неопределено не равна. ЗначениеЗаполнено(Значение) корректно работает и для ссылок, и для чисел, строк, дат.
Сравнение справочников: реквизиты, табличные части и иерархия
Реквизиты: что изменилось перед записью
Самая частая задача — понять, какие реквизиты пользователь поменял. Для этого в модуле объекта, в обработчике ПередЗаписью, читают старые значения из базы и сравнивают с текущими. Пример для справочника контрагентов:
Процедура ПередЗаписью(Отказ)
Если ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
Если ЭтоНовый() Тогда
Возврат;
КонецЕсли;
Старые = ОбщегоНазначения.ЗначенияРеквизитовОбъекта(Ссылка, "ИНН,КПП");
Если Старые.ИНН <> ИНН Или Старые.КПП <> КПП Тогда
ДополнительныеСвойства.Вставить("РеквизитыИзменились", Истина);
КонецЕсли;
КонецПроцедуры
Флаг в ДополнительныеСвойства затем читают в ПриЗаписи, когда запись уже прошла. ЗначенияРеквизитовОбъекта() читает из базы только перечисленные поля, а не весь объект — это быстрее, чем Ссылка.ИНН.
Если нужно лишь узнать, менялся ли объект в памяти после чтения, есть метод Модифицированность(). Но он не говорит, какие поля поменялись, и ставится даже при присваивании того же значения.
Табличные части
Табличную часть выгружают в таблицу значений и сравнивают содержимое (функция приведена в разделе о коллекциях):
Новая = Объект.КонтактнаяИнформация.Выгрузить();
Старая = Ссылка.КонтактнаяИнформация.Выгрузить();
Совпадают = ТаблицыРавны(Новая, Старая, "Тип,Вид,Представление");
Обращение Ссылка.ТабличнаяЧасть читает объект из базы. В массовой обработке так не делают — там нужен запрос (см. раздел об оптимизации).
Иерархия
- 📁
Элемент.ЭтоГруппа— группа или элемент. - 🔼
Элемент.Родитель = Группа— лежит ли элемент непосредственно в группе. - 🌳
Элемент.ПринадлежитЭлементу(Группа)— лежит ли элемент в группе на любом уровне вложенности. - 🔎 В запросе — условие
ГДЕ Т.Ссылка В ИЕРАРХИИ (&Группа): попадут сама группа и всё её содержимое.
Как сравнить документы: шапка, статусы, движения и проводки
У документа сравнивают три слоя: шапку, состояние и движения. Шапку и табличные части проверяют так же, как у справочника. Состояние — это стандартные реквизиты Проведен и ПометкаУдаления, плюс Дата и Номер.
С датой есть ловушка: она хранится со временем. Два документа «от одного дня» с разным временем через = окажутся разными:
ОдинДень = НачалоДня(Док1.Дата) = НачалоДня(Док2.Дата);
Движения по регистрам
Движения документа лежат в наборах записей. Набор читают и выгружают в таблицу:
Набор = РегистрыНакопления.ОстаткиТоваров.СоздатьНаборЗаписей();
Набор.Отбор.Регистратор.Установить(Документ);
Набор.Прочитать();
Движения = Набор.Выгрузить();
Имя регистра в примере условное — подставьте регистр своей конфигурации.
Проводки двух документов
Классическая задача: документ перепровели, и надо понять, чем новые проводки отличаются от проводок эталонного документа. Удобнее всего сделать это одним запросом: суммы первого документа берутся с плюсом, второго — с минусом, после группировки остаются только расхождения.
ВЫБРАТЬ
Х.СчетДт КАК СчетДт,
Х.СчетКт КАК СчетКт,
ВЫБОР
КОГДА Х.Регистратор = &Документ1
ТОГДА Х.Сумма
ИНАЧЕ -Х.Сумма
КОНЕЦ КАК Разница
ПОМЕСТИТЬ ВТ_Проводки
ИЗ
РегистрБухгалтерии.Хозрасчетный КАК Х
ГДЕ
Х.Регистратор В (&Документ1, &Документ2)
;
////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ.СчетДт КАК СчетДт,
ВТ.СчетКт КАК СчетКт,
СУММА(ВТ.Разница) КАК Разница
ИЗ
ВТ_Проводки КАК ВТ
СГРУППИРОВАТЬ ПО
ВТ.СчетДт,
ВТ.СчетКт
ИМЕЮЩИЕ
СУММА(ВТ.Разница) <> 0
Пустой результат значит, что проводки совпадают по счетам и суммам. Если нужно сверять и аналитику, добавьте в выборку и группировку нужные субконто.
Сравнение регистров запросом: остатки, обороты, два источника
Для данных регистров язык запросов почти всегда лучше кода: сравнение идёт на стороне СУБД, а в память попадают только расхождения.
Способ 1. ПОЛНОЕ СОЕДИНЕНИЕ
Подходит, когда нужно видеть обе стороны рядом. Пример: остатки на две даты.
ВЫБРАТЬ
ЕСТЬNULL(Н.Номенклатура, К.Номенклатура) КАК Номенклатура,
ЕСТЬNULL(Н.КоличествоОстаток, 0) КАК НаДату1,
ЕСТЬNULL(К.КоличествоОстаток, 0) КАК НаДату2
ИЗ
РегистрНакопления.ОстаткиТоваров.Остатки(&Дата1, ) КАК Н
ПОЛНОЕ СОЕДИНЕНИЕ РегистрНакопления.ОстаткиТоваров.Остатки(&Дата2, ) КАК К
ПО Н.Номенклатура = К.Номенклатура
ГДЕ
ЕСТЬNULL(Н.КоличествоОстаток, 0) <> ЕСТЬNULL(К.КоличествоОстаток, 0)
ЕСТЬNULL обязателен. Строка, которой нет с одной из сторон, приходит с NULL, а сравнение с NULL никогда не даёт «истину» — расхождение молча пропадёт.
Способ 2. ОБЪЕДИНИТЬ ВСЕ со знаком
Тот же приём, что для проводок: первый источник с плюсом, второй с минусом, СГРУППИРОВАТЬ ПО и ИМЕЮЩИЕ СУММА(...) <> 0. На больших объёмах он обычно работает быстрее соединения и проще расширяется на третий источник.
| Способ | Когда брать | Ограничения |
|---|---|---|
| ПОЛНОЕ СОЕДИНЕНИЕ | Нужно показать значения обеих сторон | Медленнее на больших таблицах, нужен ЕСТЬNULL |
| ОБЪЕДИНИТЬ ВСЕ + знак | Нужна только разница | Показывает сумму расхождения, а не обе стороны |
| Код (ТаблицаЗначений) | Данные уже в памяти или из внешнего файла | Всё грузится на сервер 1С |
Строки неограниченной длины в запросе сравнивать нельзя. Приведите их: ВЫРАЗИТЬ(Т.Комментарий КАК СТРОКА(1000)).
Сравнение коллекций: структуры, массивы и таблицы значений
Структура
Функция СтруктурыРавны(Структура1, Структура2)
Если Структура1.Количество() <> Структура2.Количество() Тогда
Возврат Ложь;
КонецЕсли;
Для Каждого КлючИЗначение Из Структура1 Цикл
Значение2 = Неопределено;
Если Не Структура2.Свойство(КлючИЗначение.Ключ, Значение2) Тогда
Возврат Ложь;
КонецЕсли;
Если КлючИЗначение.Значение <> Значение2 Тогда
Возврат Ложь;
КонецЕсли;
КонецЦикла;
Возврат Истина;
КонецФункции
Функция сравнивает один уровень. Вложенные структуры и массивы она сравнит по тождеству. Для вложенных данных в конфигурациях на БСП есть ОбщегоНазначения.ДанныеСовпадают(Данные1, Данные2) — она обходит коллекции рекурсивно.
Таблица значений без учёта порядка строк
Приём «знак и свёртка» работает и в коде:
Функция ТаблицыРавны(Таблица1, Таблица2, Колонки)
Если Таблица1.Количество() <> Таблица2.Количество() Тогда
Возврат Ложь;
КонецЕсли;
Общая = Таблица1.Скопировать(, Колонки);
Общая.Колонки.Добавить("Знак", Новый ОписаниеТипов("Число"));
Общая.ЗаполнитьЗначения(1, "Знак");
Для Каждого Строка Из Таблица2 Цикл
НоваяСтрока = Общая.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, Строка, Колонки);
НоваяСтрока.Знак = -1;
КонецЦикла;
Общая.Свернуть(Колонки, "Знак");
Для Каждого Строка Из Общая Цикл
Если Строка.Знак <> 0 Тогда
Возврат Ложь;
КонецЕсли;
КонецЦикла;
Возврат Истина;
КонецФункции
Пример: в первой таблице строки «Гвозди, 10» и «Шурупы, 5», во второй те же строки в обратном порядке. После свёртки у обеих строк Знак = 1 − 1 = 0 — таблицы равны. Если во второй таблице «Шурупы, 6», останутся две строки со знаком 1 и −1, функция вернёт Ложь.
Если порядок строк важен, сравнивайте построчно по индексу: Таблица1[Индекс].Колонка = Таблица2[Индекс].Колонка.
Через сериализацию
Быстрый способ для простых коллекций — сравнить строки ЗначениеВСтрокуВнутр(А) = ЗначениеВСтрокуВнутр(Б). Минус: у структур важен порядок добавления ключей, и две равные по смыслу структуры, собранные в разном порядке, дадут разные строки.
Как ускорить массовое сравнение объектов
На сотнях объектов любой метод работает мгновенно. На десятках тысяч решает подход.
- Не разыменовывайте ссылки в цикле.
Ссылка.Реквизитдля каждой строки — это отдельное чтение объекта из базы. Один запрос по всем ссылкам сразу дешевле в разы. - Сравнивайте в запросе, а не в коде. Пусть СУБД вернёт только расхождения, а не 50 000 строк для проверки в цикле.
- Индексируйте временные таблицы. Для временной таблицы, по которой идёт соединение, добавьте
ИНДЕКСИРОВАТЬ ПОс полями соединения. - Ищите через Соответствие.
ТаблицаЗначений.Найти()внутри цикла перебирает таблицу каждый раз. Соответствие с ключом-ссылкой находит запись сразу. ДляНайтиСтроки()добавьте индекс:Таблица.Индексы.Добавить("Номенклатура"). - Режьте на порции. При сравнении миллионов записей регистра обрабатывайте по периодам или по 10–50 тысяч строк, чтобы не упереться в память сервера.
- Хешируйте длинные данные. Для сравнения больших текстов или двоичных данных сохраните контрольную сумму и сравнивайте её:
Хеш = Новый ХешированиеДанных(ХешФункция.MD5);
Хеш.Добавить(ТекстДокумента);
КонтрольнаяСумма = Хеш.ХешСумма;
Готовые инструменты: история изменений, сравнение конфигураций, внешние обработки
Не каждое сравнение нужно программировать.
- 🕓 История изменений объектов. В типовых конфигурациях на БСП хранение версий включают в разделе
Администрирование → Общие настройки. После этого в карточке объекта в меню «Ещё» появляется просмотр истории: можно выбрать две версии и получить отчёт с отличиями по реквизитам и табличным частям. Названия пунктов в разных релизах могут слегка отличаться. - 🧩 Сравнение конфигураций. В Конфигураторе:
Конфигурация → Сравнить конфигурации— показывает различия в метаданных, модулях, формах и макетах. При обновлении доработанной базы используютКонфигурация → Сравнить, объединить с конфигурацией из файла. - 🛠 Внешняя обработка. Для регулярной сверки (например, остатков с данными другой базы или выгрузкой из Excel) удобно оформить сравнение во внешнюю обработку
: запрос со знаком, таблица расхождений на форме, кнопка выгрузки. Подключается через
Администрирование → Печатные формы, отчеты и обработки → Дополнительные отчеты и обработки. - 📊 Отчёты на СКД. Расхождения двух регистров можно вывести отчётом с тем же запросом — пользователь сам задаёт период и отборы без программиста.
Перед массовой правкой по итогам сравнения сделайте копию базы. Обработка, которая «исправляет расхождения», при ошибке в условии испортит все записи разом.