Как исключить определенную строку (номенклатуру) из запроса в 1С:Предприятие 8

Программист 1С v8.3 (Управляемые формы) 1C:Бухгалтерия Бухгалтерский учет IT и автоматизация бизнеса
← На главную

При разработке отчетов и обработок в среде «1С:Предприятие 8» программисты часто сталкиваются с необходимостью отсечь определенные данные из результирующей выборки. Начинающие разработчики нередко пытаются решить эту задачу «в лоб» — прямо в цикле обхода выборки, совершая при этом грубые логические и синтаксические ошибки. В этой статье мы подробно разберем, почему такие попытки терпят неудачу, проанализируем типичные ошибки и рассмотрим профессиональные варианты решения этой задачи на уровне запроса СУБД.

Анализ критических ошибок начинающих разработчиков

Рассмотрим ситуацию, когда разработчик пытается убрать из выборки строку с конкретным наименованием номенклатуры (например, «Арматура уст. АФК2Ш-65х21К1М4 скв.22И») во время обхода результата запроса. Обычно неудачные попытки выглядят следующим образом:


Пока Выборка.Следующий() Цикл
    Если Выборка.Наименование = ЗначениеЗаполнено("Арматура уст. АФК2Ш-65х21К1М4 скв.22И") Тогда
        Выборка.Наименование = Ложь; // Попытка очистить или пропустить строку
    КонецЕсли;
КонецЦикла;

Давайте разберем по шагам, почему данный код абсолютно неработоспособен и вызывает справедливую критику со стороны опытных специалистов:

  1. Непонимание логики работы функции ЗначениеЗаполнено(). Данная функция предназначена исключительно для проверки того, отличается ли переданное значение от пустого значения своего типа (например, пустой ссылки, пустой строки, нуля или значения Неопределено). Поскольку переданная строка с наименованием арматуры не является пустой, функция ЗначениеЗаполнено() всегда возвращает булево значение Истина. В итоге условие превращается в бессмысленное сравнение: Если Выборка.Наименование = Истина Тогда.... Так как в поле Наименование хранится ссылка на элемент справочника, сравнение ссылки с булевым значением всегда будет ложным.
  2. Попытка изменения данных в объекте выборки. Объект типа ВыборкаИзРезультатаЗапроса предназначен исключительно для последовательного чтения данных из СУБД (доступ «только для чтения»). Попытка присвоить новое значение свойству выборки (например, Выборка.Наименование = Ложь) вызовет критическую ошибку выполнения, так как свойства выборки динамически не перезаписываются.

Ниже представлены правильные и оптимальные способы решения этой задачи.

Вариант 1. Исключение на уровне параметров виртуальной таблицы (Лучшая практика)

Самый эффективный способ убрать ненужные данные — не выбирать их из базы данных вовсе. Это снижает нагрузку на сеть, оперативную память сервера приложений и СУБД. В запросе автора темы используются обороты регистра бухгалтерии. Для оптимизации работы мы должны накладывать условия не в секции ГДЕ, а непосредственно в параметрах виртуальной таблицы.

Выясним причину: если наложить условие в секции ГДЕ, СУБД сначала сформирует временную таблицу со всеми оборотами по счету, а затем будет производить фильтрацию по текстовым полям или ссылкам. Если же наложить условие в параметрах виртуальной таблицы, СУБД применит фильтр на этапе сборки итогов, что значительно ускорит выполнение запроса.

Рассмотрим пример правильного построения запроса с передачей исключаемого элемента через параметры:


ВЫБРАТЬ
    МАКСИМУМ(ХозрасчетныйОборотыДтКт.Регистратор) КАК Док,
    ХозрасчетныйОборотыДтКт.СубконтоДт1 КАК Наименование,
    ХозрасчетныйОборотыДтКт.СубконтоДт2 КАК Разделитель,
    СУММА(ХозрасчетныйОборотыДтКт.СуммаОборот) КАК Значение
ИЗ
    РегистрБухгалтерии.Хозрасчетный.ОборотыДтКт(
        &НачалоПериода, 
        &КонецПериода, 
        Регистратор, 
        СчетДт = &СчетДт08, 
        , 
        СчетКт = &СчетКт07, 
        , 
        Организация = &Организация 
            И СубконтоДт1 <> &ИсключаемаяНоменклатура
    ) КАК ХозрасчетныйОборотыДтКт
ГДЕ
    (ХозрасчетныйОборотыДтКт.СубконтоДт2 = &СубконтоДт21
        ИЛИ ХозрасчетныйОборотыДтКт.СубконтоДт2 = &СубконтоДт22)

СГРУППИРОВАТЬ ПО
    ХозрасчетныйОборотыДтКт.SubkontoDt1,
    ХозрасчетныйОборотыДтКт.SubkontoDt2

Для работы этого запроса во встроенном языке 1С необходимо предварительно найти исключаемую номенклатуру и передать ее в параметры запроса:


СсылкаИсключение = Справочники.Номенклатура.НайтиПоНаименованию("Арматура уст. АФК2Ш-65х21К1М4 скв.22И");

Запрос = Новый Запрос;
Запрос.Текст = // (вставить текст запроса выше);
Запрос.УстановитьПараметр("НачалоПериода", НачалоПериода);
Запрос.УстановитьПараметр("КонецПериода", КонецПериода);
Запрос.УстановитьПараметр("СчетДт08", ПланыСчетов.Хозрасчетный.ВложенияВВнеоборотныеАктивы);
Запрос.УстановитьПараметр("СчетКт07", ПланыСчетов.Хозрасчетный.ОборудованиеКУстановке);
Запрос.УстановитьПараметр("Организация", ВыбОрганизация);
Запрос.УстановитьПараметр("СубконтоДт21", Субконто21);
Запрос.УстановитьПараметр("СубконтоДт22", Субконто22);
Запрос.УстановитьПараметр("ИсключаемаяНоменклатура", СсылкаИсключение);

РезультатЗапроса = Запрос.Выполнить();

Вариант 2. Динамическое исключение списка значений (Масштабируемость)

Проанализируем ситуацию: сегодня вам нужно исключить только одну позицию арматуры, а завтра бизнес-пользователи попросят убрать еще несколько позиций. Писать жесткие сравнения через операторы <> для каждого нового элемента — неэффективно. Гораздо правильнее использовать передачу списка исключений.

Для этого в параметрах виртуальной таблицы мы будем использовать оператор НЕ ... В (&СписокИсключений). Рассмотрим реализацию этого подхода:


// В параметрах виртуальной таблицы ОборотыДтКт:
И НЕ СубконтоДт1 В (&СписокИсключений)

В коде встроенного языка мы формируем массив или список значений, который затем передаем в параметры запроса:


СписокИсключений = Новый Массив;
// Находим ссылки любым удобным и надежным способом
Ссылка1 = Справочники.Номенклатура.НайтиПоРеквизиту("Артикул", "АРТ-10023");
Если ЗначениеЗаполнено(Ссылка1) Тогда
    СписокИсключений.Добавить(Ссылка1);
КонецЕсли;

Запрос.УстановитьПараметр("СписокИсключений", СписокИсключений);

Вариант 3. Использование конструкции ЗНАЧЕНИЕ() (Без передачи параметров)

Если исключаемый элемент является предопределенным (то есть он создан на этапе конфигурирования в конфигураторе и его имя известно заранее), мы можем обратиться к нему напрямую в тексте запроса без использования параметров встроенного языка. Для этого служит ключевое слово ЗНАЧЕНИЕ():


// В параметрах виртуальной таблицы:
И НЕ СубконтоДт1 = Значение(Справочник.Номенклатура.АрматураСпецПредопределенная)

Важное примечание: Этот способ работает только для предопределенных элементов конфигурации. Обычные элементы, созданные пользователями в режиме предприятия, таким образом отфильтровать нельзя.

Вариант 4. Почему нельзя фильтровать по наименованию (строке)

Иногда у разработчиков возникает соблазн написать условие прямо по строке наименования, например: СубконтоДт1.Наименование <> "Арматура...". Мы настоятельно рекомендуем избегать этого подхода по двум важным причинам:

  1. Проблема производительности СУБД (Разыменование ссылок составного типа). Поле СубконтоДт1 на регистре бухгалтерии обычно имеет составной тип данных. Когда вы обращаетесь через точку к реквизиту поля составного типа (СубконтоДт1.Наименование), СУБД вынуждена выполнять неявные левые соединения (LEFT JOIN) со всеми таблицами, входящими в этот тип данных. Это приводит к колоссальному падению скорости выполнения запроса на больших базах данных.
  2. Ненадежность данных. Если конечный пользователь изменит хотя бы одну букву, пробел или регистр символов в наименовании номенклатуры в режиме «1С:Предприятие», жестко прописанное строковое условие перестанет работать, и исключаемый элемент снова появится в отчете. Всегда надежнее сравнивать ссылки на объекты, а не их строковые представления.

Вариант 5. Пропуск строк при обходе выборки в коде

В редких случаях, когда запрос формируется внутри закрытого программного кода стороннего модуля, изменить сам текст запроса невозможно. В этой ситуации мы можем выполнить фильтрацию при обходе результатов в цикле. Для этого используется оператор Продолжить, который позволяет пропустить текущую итерацию цикла и перейти к следующей:


СсылкаИсключение = Справочники.Номенклатура.НайтиПоНаименованию("Арматура уст. АФК2Ш-65х21К1М4 скв.22И");

Выборка = РезультатЗапроса.Выбрать();
ИтоговаяСумма = 0;

Пока Выборка.Следующий() Цикл
    // Правильное сравнение ссылочных типов данных
    Если Выборка.Наименование = СсылкаИсключение Тогда
        Продолжить; // Пропускаем текущую строку, код ниже выполняться не будет
    КонецЕсли;
    
    // Выполняем полезную работу только для нужных строк
    ИтоговаяСумма = ИтоговаяСумма + Выборка.Значение;
КонецЦикла;

Этот способ уступает фильтрации в запросе по производительности, поскольку сервер 1С все равно получает лишние строки из базы данных, но он гарантирует корректность расчетов и не вызывает ошибок работы с типами данных.

← На главную