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