При разработке отчетов и обработок в среде «1С:Предприятие 8» программисты часто сталкиваются с необходимостью отсечь определенные данные из результирующей выборки. Начинающие разработчики нередко пытаются решить эту задачу «в лоб» — прямо в цикле обхода выборки, совершая при этом грубые логические и синтаксические ошибки. В этой статье мы подробно разберем, почему такие попытки терпят неудачу, проанализируем типичные ошибки и рассмотрим профессиональные варианты решения этой задачи на уровне запроса СУБД.
Рассмотрим ситуацию, когда разработчик пытается убрать из выборки строку с конкретным наименованием номенклатуры (например, «Арматура уст. АФК2Ш-65х21К1М4 скв.22И») во время обхода результата запроса. Обычно неудачные попытки выглядят следующим образом:
Пока Выборка.Следующий() Цикл
Если Выборка.Наименование = ЗначениеЗаполнено("Арматура уст. АФК2Ш-65х21К1М4 скв.22И") Тогда
Выборка.Наименование = Ложь; // Попытка очистить или пропустить строку
КонецЕсли;
КонецЦикла;
Давайте разберем по шагам, почему данный код абсолютно неработоспособен и вызывает справедливую критику со стороны опытных специалистов:
ЗначениеЗаполнено(). Данная функция предназначена исключительно для проверки того, отличается ли переданное значение от пустого значения своего типа (например, пустой ссылки, пустой строки, нуля или значения Неопределено). Поскольку переданная строка с наименованием арматуры не является пустой, функция ЗначениеЗаполнено() всегда возвращает булево значение Истина. В итоге условие превращается в бессмысленное сравнение: Если Выборка.Наименование = Истина Тогда.... Так как в поле Наименование хранится ссылка на элемент справочника, сравнение ссылки с булевым значением всегда будет ложным.ВыборкаИзРезультатаЗапроса предназначен исключительно для последовательного чтения данных из СУБД (доступ «только для чтения»). Попытка присвоить новое значение свойству выборки (например, Выборка.Наименование = Ложь) вызовет критическую ошибку выполнения, так как свойства выборки динамически не перезаписываются.Ниже представлены правильные и оптимальные способы решения этой задачи.
Самый эффективный способ убрать ненужные данные — не выбирать их из базы данных вовсе. Это снижает нагрузку на сеть, оперативную память сервера приложений и СУБД. В запросе автора темы используются обороты регистра бухгалтерии. Для оптимизации работы мы должны накладывать условия не в секции ГДЕ, а непосредственно в параметрах виртуальной таблицы.
Выясним причину: если наложить условие в секции ГДЕ, СУБД сначала сформирует временную таблицу со всеми оборотами по счету, а затем будет производить фильтрацию по текстовым полям или ссылкам. Если же наложить условие в параметрах виртуальной таблицы, СУБД применит фильтр на этапе сборки итогов, что значительно ускорит выполнение запроса.
Рассмотрим пример правильного построения запроса с передачей исключаемого элемента через параметры:
ВЫБРАТЬ
МАКСИМУМ(ХозрасчетныйОборотыДтКт.Регистратор) КАК Док,
ХозрасчетныйОборотыДтКт.СубконтоДт1 КАК Наименование,
ХозрасчетныйОборотыДтКт.СубконтоДт2 КАК Разделитель,
СУММА(ХозрасчетныйОборотыДтКт.СуммаОборот) КАК Значение
ИЗ
РегистрБухгалтерии.Хозрасчетный.ОборотыДтКт(
&НачалоПериода,
&КонецПериода,
Регистратор,
СчетДт = &СчетДт08,
,
СчетКт = &СчетКт07,
,
Организация = &Организация
И СубконтоДт1 <> &ИсключаемаяНоменклатура
) КАК ХозрасчетныйОборотыДтКт
ГДЕ
(ХозрасчетныйОборотыДтКт.СубконтоДт2 = &СубконтоДт21
ИЛИ ХозрасчетныйОборотыДтКт.СубконтоДт2 = &СубконтоДт22)
СГРУППИРОВАТЬ ПО
ХозрасчетныйОборотыДтКт.SubkontoDt1,
ХозрасчетныйОборотыДтКт.SubkontoDt2
Для работы этого запроса во встроенном языке 1С необходимо предварительно найти исключаемую номенклатуру и передать ее в параметры запроса:
СсылкаИсключение = Справочники.Номенклатура.НайтиПоНаименованию("Арматура уст. АФК2Ш-65х21К1М4 скв.22И");
Запрос = Новый Запрос;
Запрос.Текст = // (вставить текст запроса выше);
Запрос.УстановитьПараметр("НачалоПериода", НачалоПериода);
Запрос.УстановитьПараметр("КонецПериода", КонецПериода);
Запрос.УстановитьПараметр("СчетДт08", ПланыСчетов.Хозрасчетный.ВложенияВВнеоборотныеАктивы);
Запрос.УстановитьПараметр("СчетКт07", ПланыСчетов.Хозрасчетный.ОборудованиеКУстановке);
Запрос.УстановитьПараметр("Организация", ВыбОрганизация);
Запрос.УстановитьПараметр("СубконтоДт21", Субконто21);
Запрос.УстановитьПараметр("СубконтоДт22", Субконто22);
Запрос.УстановитьПараметр("ИсключаемаяНоменклатура", СсылкаИсключение);
РезультатЗапроса = Запрос.Выполнить();
Проанализируем ситуацию: сегодня вам нужно исключить только одну позицию арматуры, а завтра бизнес-пользователи попросят убрать еще несколько позиций. Писать жесткие сравнения через операторы <> для каждого нового элемента — неэффективно. Гораздо правильнее использовать передачу списка исключений.
Для этого в параметрах виртуальной таблицы мы будем использовать оператор НЕ ... В (&СписокИсключений). Рассмотрим реализацию этого подхода:
// В параметрах виртуальной таблицы ОборотыДтКт:
И НЕ СубконтоДт1 В (&СписокИсключений)
В коде встроенного языка мы формируем массив или список значений, который затем передаем в параметры запроса:
СписокИсключений = Новый Массив;
// Находим ссылки любым удобным и надежным способом
Ссылка1 = Справочники.Номенклатура.НайтиПоРеквизиту("Артикул", "АРТ-10023");
Если ЗначениеЗаполнено(Ссылка1) Тогда
СписокИсключений.Добавить(Ссылка1);
КонецЕсли;
Запрос.УстановитьПараметр("СписокИсключений", СписокИсключений);
Если исключаемый элемент является предопределенным (то есть он создан на этапе конфигурирования в конфигураторе и его имя известно заранее), мы можем обратиться к нему напрямую в тексте запроса без использования параметров встроенного языка. Для этого служит ключевое слово ЗНАЧЕНИЕ():
// В параметрах виртуальной таблицы:
И НЕ СубконтоДт1 = Значение(Справочник.Номенклатура.АрматураСпецПредопределенная)
Важное примечание: Этот способ работает только для предопределенных элементов конфигурации. Обычные элементы, созданные пользователями в режиме предприятия, таким образом отфильтровать нельзя.
Иногда у разработчиков возникает соблазн написать условие прямо по строке наименования, например: СубконтоДт1.Наименование <> "Арматура...". Мы настоятельно рекомендуем избегать этого подхода по двум важным причинам:
СубконтоДт1 на регистре бухгалтерии обычно имеет составной тип данных. Когда вы обращаетесь через точку к реквизиту поля составного типа (СубконтоДт1.Наименование), СУБД вынуждена выполнять неявные левые соединения (LEFT JOIN) со всеми таблицами, входящими в этот тип данных. Это приводит к колоссальному падению скорости выполнения запроса на больших базах данных.В редких случаях, когда запрос формируется внутри закрытого программного кода стороннего модуля, изменить сам текст запроса невозможно. В этой ситуации мы можем выполнить фильтрацию при обходе результатов в цикле. Для этого используется оператор Продолжить, который позволяет пропустить текущую итерацию цикла и перейти к следующей:
СсылкаИсключение = Справочники.Номенклатура.НайтиПоНаименованию("Арматура уст. АФК2Ш-65х21К1М4 скв.22И");
Выборка = РезультатЗапроса.Выбрать();
ИтоговаяСумма = 0;
Пока Выборка.Следующий() Цикл
// Правильное сравнение ссылочных типов данных
Если Выборка.Наименование = СсылкаИсключение Тогда
Продолжить; // Пропускаем текущую строку, код ниже выполняться не будет
КонецЕсли;
// Выполняем полезную работу только для нужных строк
ИтоговаяСумма = ИтоговаяСумма + Выборка.Значение;
КонецЦикла;
Этот способ уступает фильтрации в запросе по производительности, поскольку сервер 1С все равно получает лишние строки из базы данных, но он гарантирует корректность расчетов и не вызывает ошибок работы с типами данных.