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