При разработке отчетов или механизмов контроля (особенно в системах маркировки МДЛП) часто возникает необходимость сопоставить данные двух документов: например, «Отпуска товаров в отделение» и «Уведомления о выдаче». Основная сложность заключается в том, что связь по номенклатуре и серии не всегда является уникальной, что приводит к «размножению» строк (эффекту декартова произведения) при использовании обычных соединений ЛЕВОЕ СОЕДИНЕНИЕ или ПОЛНОЕ СОЕДИНЕНИЕ. Разберем подробно, как решить эту проблему несколькими способами.
Рассмотрим стандартную ситуацию. В первом документе у нас есть две строки с одной и той же номенклатурой и серией (разные упаковки или просто разные строки), и во втором документе аналогично. Если мы просто свяжем их по полям Номенклатура и Серия, СУБД сопоставит каждую строку из первой таблицы с каждой строкой из второй. В результате вместо двух или трех строк мы получим четыре или более, а итоговые показатели (количество) будут завышены в несколько раз.
Проанализируем предложенные варианты решения проблемы, от простых соединений до наиболее надежной методики объединения.
Этот метод считается эталонным в 1С для задач сопоставления. Вместо того чтобы «склеивать» таблицы боками, мы помещаем их друг под друга, разделяя ресурсы (количества) по разным колонкам, а затем «схлопываем» их через группировку. Рассмотрим этот подход по шагам.
Сначала мы выбираем данные из первого документа (Отпуск) и создаем «пустую» колонку для данных второго документа. Затем добавляем данные из второго документа (Уведомление) через ОБЪЕДИНИТЬ ВСЕ, оставляя «пустой» колонку первого документа.
ВЫБРАТЬ
ВложенныйЗапрос.Ссылка КАК ДокументОснование,
ВложенныйЗапрос.Номенклатура КАК Номенклатура,
ВложенныйЗапрос.Серия КАК Серия,
СУММА(ВложенныйЗапрос.КоличествоОтпуск) КАК КоличествоОтпуск,
СУММА(ВложенныйЗапрос.КоличествоУведомление) КАК КоличествоУведомление
ИЗ
(ВЫБРАТЬ
ОтпускТовары.Ссылка КАК Ссылка,
ОтпускТовары.Номенклатура КАК Номенклатура,
ОтпускТовары.СерияНоменклатуры КАК Серия,
ОтпускТовары.Количество КАК КоличествоОтпуск,
0 КАК КоличествоУведомление
ИЗ
Документ.ОтпускТоваровВОтделение.Товары КАК ОтпускТовары
ГДЕ
ОтпускТовары.Ссылка = &Ссылка
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
УведомлениеТовары.Ссылка.Основание,
УведомлениеТовары.Номенклатура,
УведомлениеТовары.Серия,
0,
УведомлениеТовары.Количество
ИЗ
Документ.УведомлениеОВыдачеВОтделенияМДЛП.Товары КАК УведомлениеТовары
ГДЕ
УведомлениеТовары.Ссылка.Основание = &Ссылка) КАК ВложенныйЗапрос
СГРУППИРОВАТЬ ПО
ВложенныйЗапрос.Ссылка,
ВложенныйЗапрос.Номенклатура,
ВложенныйЗапрос.Серия
Преимущества этого метода:
Если вам критично использовать именно соединения (например, когда нужно выводить специфические реквизиты каждого документа в разных колонках), следует использовать ПОЛНОЕ СОЕДИНЕНИЕ. Это позволит увидеть строки, которые есть в первом документе, но отсутствуют во втором, и наоборот.
Важный нюанс: при полном соединении поля связи могут содержать NULL, если соответствие не найдено. Поэтому в полях ВЫБРАТЬ и в условии ГДЕ обязательно используйте функцию ЕСТЬNULL.
ВЫБРАТЬ
ЕСТЬNULL(ВТ_Отпуска.Номенклатура, ВТ_Уведомления.Номенклатура) КАК Номенклатура,
ЕСТЬNULL(ВТ_Отпуска.СерияНоменклатуры, ВТ_Уведомления.Серия) КАК Серия,
ЕСТЬNULL(ВТ_Отпуска.Количество, 0) КАК КоличествоОтпуска,
ЕСТЬNULL(ВТ_Уведомления.Количество, 0) КАК КоличествоВУведомлении
ИЗ
ВТ_Отпуска КАК ВТ_Отпуска
ПОЛНОЕ СОЕДИНЕНИЕ ВТ_Уведомления КАК ВТ_Уведомления
ПО ВТ_Отпуска.Номенклатура = ВТ_Уведомления.Номенклатура
И ВТ_Отпуска.СерияНоменклатуры = ВТ_Уведомления.Серия
Рассмотрим подробнее проблему дублей в этом варианте. Если в ВТ_Отпуска будет две строки с одной серией, а в ВТ_Уведомления — одна, вы получите две строки в результате, но количество из уведомления «размножится» на обе строки. Чтобы этого избежать, данные в исходных временных таблицах должны быть предварительно сгруппированы по ключам связи (Номенклатура + Серия).
Иногда требуется сопоставить строки именно «один к одному», даже если у них одинаковая номенклатура и серия. В этом случае мы можем добавить уникальный индекс для каждой строки внутри документа. В 1С это можно реализовать с помощью функции АВТОНОМЕРЗАПИСИ() во временных таблицах.
Выясним причину, зачем это нужно: если в документе отпуска две строки «Спирт, серия 001, 5 литров», и в уведомлении тоже две такие же строки, то обычное соединение даст 4 строки. Если же мы пронумеруем строки в каждом документе (1 и 2) и добавим НомерСтроки в условие связи, мы получим ровно 2 строки в результате.
// В первой временной таблице нумеруем строки
ВЫБРАТЬ
ТЧ.Номенклатура,
ТЧ.Серия,
ТЧ.Количество,
АВТОНОМЕРЗАПИСИ() КАК НомерСтроки
ПОМЕСТИТЬ ВТ_Отпуска_Индекс
ИЗ
Документ.Отпуск.Товары КАК ТЧ;
// При соединении используем номер строки как часть ключа
ВЫБРАТЬ ...
ИЗ ВТ_Отпуска_Индекс КАК Т1
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Уведомления_Индекс КАК Т2
ПО Т1.Номенклатура = Т2.Номенклатура
И Т1.Серия = Т2.Серия
И Т1.НомерСтроки = Т2.НомерСтроки
Разберем ситуацию, когда в одном документе серия заполнена, а во втором — нет. Это часто бывает, если документ-основание заполнен частично или серии были изменены в процессе маркировки. Чтобы запрос не терял такие данные:
ОБЪЕДИНИТЬ ВСЕ группировка по пустому значению серии объединит все «бессерийные» позиции в одну строку, что обычно и требуется для сверки общих количеств.СправочникСсылка.СерииНоменклатуры и просто текстовая строка в разных документах), приводите их к единому виду или связывайте через дополнительные реквизиты (например, номер серии).Документ.Проведен = ИСТИНА, чтобы не анализировать черновики.Для корректного решения задачи мы рекомендуем придерживаться следующего алгоритма:
NULL и группировки.ПОЛНОЕ СОЕДИНЕНИЕ всегда оборачивайте выбираемые поля в ЕСТЬNULL, чтобы результат был читаемым и пригодным для дальнейших вычислений.