При автоматической загрузке заказов из внешних источников (интернет-магазины, маркетплейсы, сервисы электронного обмена) нередко возникает ситуация, когда на один фактический заказ формируется несколько одинаковых документов. В таких случаях реквизиты шапки (дата, склад, организация) и состав табличной части (номенклатура, характеристика, серия, цена, количество) полностью идентичны, но документы имеют разные номера и ссылки.
Разберем, почему стандартное соединение табличных частей приводит к дублированию строк в отчетах и как правильно составить запрос на языке 1С для поиска полных дублей, а также какие альтернативные подходы можно использовать.
Проанализируем типовую ошибку при попытке найти задвоенные документы. Когда мы связываем документ сам с собой по полям табличной части, соединение сопоставляет каждую совпадающую позицию. Однако если в первом документе 5 строк, а во втором — 3 строки, и эти 3 строки совпадают с позициями первого документа, простое внутреннее или левое соединение сочтет такие документы связанными. Это приведет к ложным срабатываниям: документ с частью совпадающих позиций ошибочно определится как дубль.
Кроме того, в языке запросов платформы 1С нет встроенных средств агрегации строк (по аналогии со строковой конкатенацией в реляционных СУБД), поэтому «склеить» всю табличную часть в одну строку прямо в тексте запроса невозможно.
Рассмотрим элегантный математический алгоритм. Два документа считаются дубликатами по составу табличной части, если одновременно выполняются три условия:
Посмотрим на практическую реализацию этого алгоритма с помощью пакетного запроса:
ВЫБРАТЬ
ЗаказПокупателяТовары.Ссылка КАК Ссылка,
ЗаказПокупателяТовары.Номенклатура КАК Номенклатура,
ЗаказПокупателяТовары.ХарактеристикаНоменклатуры КАК ХарактеристикаНоменклатуры,
ЗаказПокупателяТовары.СерияНоменклатуры КАК СерияНоменклатуры,
ЗаказПокупателяТовары.Цена КАК Цена,
ЗаказПокупателяТовары.Количество КАК Количество,
1 КАК СтрокВОригинале
ПОМЕСТИТЬ ВТ_Оригиналы
ИЗ
Документ.ЗаказПокупателя.Товары КАК ЗаказПокупателяТовары
ГДЕ
НАЧАЛОПЕРИОДА(ЗаказПокупателяТовары.Ссылка.Дата, ДЕНЬ) = НАЧАЛОПЕРИОДА(&ДатаАнализа, ДЕНЬ)
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
Оригиналы.Ссылка КАК Ссылка,
ПотенциальныеДубли.Ссылка КАК Дубль,
СУММА(Оригиналы.СтрокВОригинале) КАК СтрокВОригинале,
СУММА(ПотенциальныеДубли.СтрокВОригинале) КАК СтрокВДубле,
СУММА(1) КАК Совпадений
ПОМЕСТИТЬ ВТ_Сравнение
ИЗ
ВТ_Оригиналы КАК Оригиналы
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Оригиналы КАК ПотенциальныеДубли
ПО Оригиналы.Ссылка <> ПотенциальныеДубли.Ссылка
И Оригиналы.Номенклатура = ПотенциальныеДубли.Номенклатура
И Оригиналы.ХарактеристикаНоменклатуры = ПотенциальныеДубли.ХарактеристикаНоменклатуры
И Оригиналы.СерияНоменклатуры = ПотенциальныеДубли.СерияНоменклатуры
И Оригиналы.Цена = ПотенциальныеДубли.Цена
И Оригиналы.Количество = ПотенциальныеДубли.Количество
СГРУППИРОВАТЬ ПО
Оригиналы.Ссылка,
ПотенциальныеДубли.Ссылка
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ_Сравнение.Ссылка КАК ОсновнойДокумент,
ВТ_Сравнение.Дубль КАК ДублирующийДокумент
ИЗ
ВТ_Сравнение КАК ВТ_Сравнение
ГДЕ
ВТ_Сравнение.СтрокВОригинале = ВТ_Сравнение.СтрокВДубле
И ВТ_Сравнение.СтрокВДубле = ВТ_Сравнение.Совпадений
Разберем по шагам, как работает этот запрос:
ВТ_Оригиналы помещаются все строки табличных частей за указанный день. Поле 1 КАК СтрокВОригинале служит счетчиком для последующего суммирования.Если документов за период создается много, самосоединение больших объемов данных в запросе может существенно нагружать СУБД. В таких ситуациях наиболее производительным решением является вычисление контрольной суммы (хеша) табличной части.
Рассмотрим, как это работает на встроенном языке 1С:
ХешированиеДанных (с алгоритмом ХешФункция.MD5 или ХешФункция.SHA256).Посмотрим на пример вычисления хеша табличной части:
Хеширование = Новый ХешированиеДанных(ХешФункция.MD5);
ТаблицаТоваров = ДокументОбъект.Товары.Выгрузить();
ТаблицаТоваров.Сортировать("Номенклатура, ХарактеристикаНоменклатуры, Цена");
Для Каждого Стр Из ТаблицаТоваров Цикл
СтрокаДанных = Строка(Стр.Номенклатура.УникальныйИдентификатор()) + ";"
+ Строка(Стр.Цена) + ";"
+ Строка(Стр.Количество) + "|";
Хеширование.Добавить(СтрокаДанных);
КонецЦикла;
ХешТабличнойЧасти = Хеширование.ХешСумма;
При таком подходе поиск дублей сводится к элементарному запросу с группировкой по реквизитам Склад, Дата и ХешТабличнойЧасти с условием ИМЕЮЩИЕ КОЛИЧЕСТВО(Ссылка) > 1, что выполняется мгновенно даже на миллионах записей.
Если необходимо быстро проанализировать небольшой объем документов без изменения метаданных конфигурации, можно выгрузить результат запроса в ТаблицаЗначений и выполнить свертку средствами встроенного языка.
Проанализируем последовательность действий:
Свернуть().Выясним причину дублирования на этапе архитектуры обмена. Чтобы исключить повторное создание заявок при повторных сетевых запросах или сбоях каналов связи, рекомендуется внедрять следующие механизмы:
ИдентификаторЗаявкиИнтернетМагазина, а ресурсом — Ссылка на созданный заказ. Перед созданием проверяйте наличие записи в регистре.БлокировкаДанных по внешнему идентификатору заявки. Это предотвратит параллельную запись одного и того же заказа несколькими фоновыми заданиями.