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