При разработке на платформе 1С:Предприятие 8 программисты часто сталкиваются с необходимостью фильтрации данных внутри цикла обхода выборки. Казалось бы, простая задача — добавить еще одно условие исключения — может превратиться в проблему, если не учитывать законы логики и особенности взаимодействия кода с базой данных. Разберем подробно, как правильно строить логические выражения, почему важно избегать поиска ссылок внутри цикла и как перенести проверку условий на сторону сервера базы данных.
Рассмотрим ситуацию, когда нам нужно обработать номенклатуру, которая не входит в несколько определенных групп. В исходном сообщении была сделана попытка использовать оператор ИЛИ с отрицанием НЕ. Проанализируем, почему выражение НЕ ВГруппе1 ИЛИ НЕ ВГруппе2 работает некорректно.
Согласно законам формальной логики (законам де Моргана), если мы хотим исключить попадание элемента в обе группы сразу, нам нужно использовать одно из двух эквивалентных выражений:
НЕ Условие1 И НЕ Условие2. Это означает, что товар одновременно не должен быть в первой группе и не должен быть во второй.НЕ (Условие1 ИЛИ Условие2). Мы сначала объединяем группы в одно множество, а затем проверяем, что элемент в него не входит.Если же написать НЕ Условие1 ИЛИ НЕ Условие2, то условие будет истинным всегда, так как один и тот же элемент номенклатуры не может находиться в двух разных родительских группах одновременно (у него только один непосредственный родитель). Он всегда будет "не в Группе 1" или "не в Группе 2", и программа пропустит все элементы без исключения.
Посмотрим на правильный пример исправления кода в цикле:
// Неправильно:
Если Единица = "796" И (НЕ ВГруппе1 ИЛИ НЕ ВГруппе2) Тогда ...
// Правильно (вариант 1):
Если Единица = "796" И (НЕ ВГруппе1 И НЕ ВГруппе2) Тогда ...
// Правильно (вариант 2):
Если Единица = "796" И НЕ (ВГруппе1 ИЛИ ВГруппе2) Тогда ...
Одной из самых грубых ошибок является использование методов поиска, таких как НайтиПоКоду() или НайтиПоНаименованию(), непосредственно внутри условия Если при обходе выборки. Разберем ситуацию: если в вашей выборке 10 000 строк, и вы дважды вызываете НайтиПоКоду() в каждой итерации, система выполнит 20 000 лишних запросов к базе данных.
Проанализируем правильный алгоритм подготовки данных:
Пример оптимизированного кода:
// 1. Подготовка данных ПЕРЕД циклом
ГруппаИсключение1 = Справочники.Номенклатура.НайтиПоКоду("0245");
ГруппаИсключение2 = Справочники.Номенклатура.НайтиПоКоду("К0244");
Пока ВыборкаНоменклатура.Следующий() Цикл
ТекущаяНоменклатура = ВыборкаНоменклатура.Номенклатура;
// 2. Быстрая проверка ссылок в памяти
Если ТекущаяНоменклатура.ЕдиницаДляОтчетов.Код = "196"
И НЕ ТекущаяНоменклатура.ПринадлежитЭлементу(ГруппаИсключение1)
И НЕ ТекущаяНоменклатура.ПринадлежитЭлементу(ГруппаИсключение2) Тогда
// Выполняем действия
КонецЕсли;
КонецЦикла;
Если групп-исключений становится много (три, пять или десять), код превращается в "простыню" из условий И НЕ.... В этом случае эффективно использовать Массив или СписокЗначений. Однако метод ПринадлежитЭлементу() работает только с одной конкретной ссылкой.
Рассмотрим, как можно изящно решить эту задачу через проверку всей иерархии. Выясним причину, по которой иногда лучше использовать Соответствие (Map) для кэширования результатов проверки. Если у вас много товаров из одних и тех же папок, можно один раз проверить папку и запомнить результат, чтобы не вызывать ПринадлежитЭлементу() для каждого товара.
Самый эффективный способ, который рекомендуют эксперты, — это перенос всей логики условий непосредственно в текст запроса. Это позволяет базе данных самой отфильтровать нужные строки или пометить их специальным флагом. Выясним, как это реализовать с помощью конструкции ВЫБОР КОГДА.
Разберем по шагам создание вычисляемого поля в запросе:
В ИЕРАРХИИ.ЭтоНужныйТовар.Пример запроса:
ТекстЗапроса =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Товар,
| Номенклатура.Сумма КАК Сумма,
| ВЫБОР
| КОГДА Номенклатура.ЕдиницаДляОтчетов.Код = ""196""
| И НЕ Номенклатура.Ссылка В ИЕРАРХИИ (&МассивГруппИсключений)
| ТОГДА ИСТИНА
| ИНАЧЕ ЛОЖЬ
| КОНЕЦ КАК ПодходитПодУсловие
|ИЗ
| Справочник.Номенклатура КАК Номенклатура";
Запрос.УстановитьПараметр("МассивГруппИсключений", СписокГрупп);
Теперь при обходе выборки вам не нужно писать сложные логические конструкции. Мы просто анализируем готовый результат:
Пока Выборка.Следующий() Цикл
Если Выборка.ПодходитПодУсловие Тогда
// Логика для группы 1
Иначе
// Логика для остальных (ИНАЧЕ)
КонецЕсли;
КонецЦикла;
Проанализировав ситуацию, мы видим, что решение проблемы кроется в комплексном подходе. Во-первых, всегда проверяйте скобки и логические операторы (И вместо ИЛИ при множественном отрицании). Во-вторых, никогда не делайте запросы к базе (поиск по коду) внутри цикла. В-третьих, стремитесь перекладывать логику фильтрации на сторону СУБД через запросы — это делает ваш код быстрее, чище и профессиональнее.