В практике программирования в среде 1С разработчики ежедневно сталкиваются с необходимостью наложения условий на выборку или обработку данных. Задачи могут варьироваться от простейшей фильтрации строк в цикле до построения сложных алгоритмов в запросах, учитывающих остатки на начало периода и обороты текущего года. Давайте подробно разберем, как правильно конструировать подобные условия, избегая типичных ошибок, снижающих производительность системы.
Начнем с простого примера. Предположим, у нас есть коллекция данных (например, табличная часть документа или таблица значений), и нам необходимо пропустить строки, в которых сумма отрицательна. На первый взгляд задача кажется тривиальной, однако здесь часто совершают критическую ошибку в именовании переменных.
Рассмотрим код, который часто пишут начинающие разработчики:
// Неправильный вариант использования зарезервированных имен
Для Каждого Строка Из ТЧДокумента Цикл
Если Строка < 0 Тогда
// Попытка исключить строку
КонецЕсли;
КонецЦикла;
Проанализируем, почему этот код не будет работать и вызовет ошибку выполнения. Дело в том, что слово Строка является зарезервированным системным типом данных в платформе 1С:Предприятие (тип данных Строка / String). Использование зарезервированных типов в качестве имен локальных переменных считается грубым нарушением стандартов разработки фирмы "1С". Более того, попытка сравнить объект строки коллекции напрямую с числом (Строка < 0) приведет к ошибке несовместимости типов.
Для исправления ситуации мы должны переименовать переменную и обратиться к конкретному числовому реквизиту этой строки. Разберем правильный вариант написания условия с использованием оператора Продолжить:
// Правильный вариант с обходом строк табличной части
Для Каждого ТекущаяСтрока Из ТЧДокумента Цикл
// Проверяем конкретное числовое поле строки
Если ТекущаяСтрока.Сумма < 0 Тогда
Продолжить; // Пропускаем текущую итерацию цикла и переходим к следующей строке
КонецЕсли;
// Здесь располагается логика обработки только положительных сумм
ОбработатьДанныеСтроки(ТекущаяСтрока);
КонецЦикла;
В данном примере мы использовали переменную ТекущаяСтрока, что исключает конфликт имен типов. Оператор Продолжить мгновенно передает управление на следующую итерацию цикла, не выполняя нижележащий код для строк с отрицательной суммой.
Рассмотрим альтернативный вариант решения этой задачи с использованием булевого флага. Этот метод удобен, если логику фильтрации нужно распределить или передать в другие процедуры:
Для Каждого СтрокаТЧ Из ТЧДокумента Цикл
БратьСуммуЭтойСтроки = Истина;
Если СтрокаТЧ.Сумма < 0 Тогда
БратьСуммуЭтойСтроки = Ложь;
КонецЕсли;
Если БратьСуммуЭтойСтроки Тогда
// Выполняем расчеты
ИтоговаяСумма = ИтоговаяСумма + СтрокаТЧ.Сумма;
КонецЕсли;
КонецЦикла;
Этот подход делает код более наглядным, если в дальнейшем логика проверки условий будет усложняться (например, добавятся проверки на заполненность аналитики или статусы проведения).
Теперь перейдем к более сложной и интересной задаче. Предположим, перед нами стоит задача в запросе: если по объекту учета есть остаток на начало года, то мы не должны исключать его обороты; если же это новый приход текущего года (то есть остатка на начало года не было), то его необходимо исключить из итоговой выборки.
Попытка решить эту задачу, используя только одну виртуальную таблицу РегистрБухгалтерии.Хозрасчетный.ОборотыДтКт, обречена на провал. Данная таблица хранит исключительно информацию о движениях (оборотах) между счетами за указанный период. Данных об остатках на начало года в ней физически нет.
Для реализации этой логики мы должны применить классический паттерн разработки в 1С: левое соединение таблицы оборотов с таблицей остатков по ключевой аналитике (в нашем случае — субконто Строительство объектов основных средств).
Разберем по шагам, как построить такое левое соединение:
Null и больше нуля, значит, остаток на начало года был — в этом случае мы берем сумму оборота. Если остатка не было (вернулся Null), то обнуляем значение.Посмотрим на пример реализации такого запроса:
ВЫБРАТЬ
Обороты.Регистратор КАК Док,
Обороты.СубконтоДт1 КАК Наименование,
Обороты.СубконтоДт2 КАК Разделитель,
// Реализуем условие: если есть остаток на начало года, берем сумму оборота, иначе — 0
ВЫБОР
КОГДА ЕСТЬNULL(ОстаткиНаНачало.СуммаОстатокДт, 0) > 0
ТОГДА Обороты.СуммаОборот
ИНАЧЕ 0
КОНЕЦ КАК Значение
ИЗ
РегистрБухгалтерии.Хозрасчетный.ОборотыДтКт(&НачалоПериода, &КонецПериода, Регистратор, СчетДт = &СчетДт08, , СчетКт = &СчетКт07, , Организация = &Организация) КАК Обороты
ЛЕВОЕ СОЕДИНЕНИЕ РегистрБухгалтерии.Хозрасчетный.Остатки(&НачалоГода, Счет = &СчетДт08, , Организация = &Организация) КАК ОстаткиНаНачало
ПО Обороты.СубконтоДт1 = ОстаткиНаНачало.Субконто1
Обратите внимание на использование функции ЕСТЬNULL. При левом соединении, если для конкретного субконто из оборотов не найдется записи в остатках на начало года, СУБД вернет значение Null. Функция ЕСТЬNULL(ОстаткиНаНачало.СуммаОстатокДт, 0) заменяет это значение на числовой ноль, предотвращая потенциальные ошибки при сравнении.
Анализируя исходный код запроса, можно выделить три критические архитектурные ошибки, которые могут привести к зависанию базы данных при росте объемов информации и снизить надежность конфигурации.
Ошибка 1. Фильтрация данных в предложении ГДЕ вместо параметров виртуальной таблицы.
В исходном коде отбор по статьям затрат (субконто) был написан так:
ИЗ РегистрБухгалтерии.Хозрасчетный.ОборотыДтКт(...) КАК ХозрасчетныйОборотыДтКт
ГДЕ (ХозрасчетныйОборотыДтКт.SubkontoDt2 = &СубконтоДт21 ИЛИ ...)
При такой конструкции СУБД сначала извлечет из базы абсолютно все обороты по счетам 08 и 07 по всем организациям за указанный период в оперативную память сервера приложений, и только после этого начнет отсекать ненужные записи. Это крайне неэффективно.
Для оптимизации все отборы по субконто и измерениям необходимо передавать в качестве параметров **внутри** самой виртуальной таблицы в поле УсловиеОтбора. В этом случае СУБД применит индексы таблиц и вернет из базы данных только те строки, которые изначально соответствуют условию.
Ошибка 2. Жесткое кодирование (хардкод) элементов по коду.
В коде инициализации параметров мы видим конструкцию Справочники.СтатьиЗатрат.НайтиПоКоду("л00000044"). Это классический пример уязвимого кода. Если в режиме "1С:Предприятие" пользователь случайно изменит код статьи затрат или при переносе данных коды изменятся, метод вернет пустую ссылку. Запрос перестанет выбирать данные, не выдавая при этом видимых ошибок.
Для исправления этой архитектурной ошибки рекомендуется:
Ошибка 3. Динамическая склейка текста запроса.
Добавление условий через конкатенацию строк вида Запрос.Текст = Запрос.Текст + " И ..." снижает читаемость кода и усложняет его отладку. Оптимальным решением является использование универсального условия внутри самого SQL-запроса с проверкой заполненности параметра.
Рассмотрим, как красиво реализовать опциональный фильтр без склеивания строк:
ГДЕ (&НеФильтроватьПоСубконто1 ИЛИ ХозрасчетныйОборотыДтКт.СубконтоДт1 = &СубконтоДт1)
Если параметр Данные заполнен, мы передаем в параметр &НеФильтроватьПоСубконто1 значение Ложь, а в &СубконтоДт1 — само значение. Если данные не заполнены, мы передаем в первый параметр значение Истина, и условие в скобках автоматически становится истинным для всех строк, отключая фильтрацию по данному субконто.
Объединим все озвученные методики оптимизации и исправления ошибок в единый, отказоустойчивый и быстрый алгоритм. Ниже представлен готовый вариант функции, оптимизированный под стандарты разработки "1С:Совместимо":
Функция ПолучитьДанныеОборотовИОстатков(НачалоПериода, КонецПериода, Организация, СтатьиЗатратСписок, ДанныеФильтра) Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Обороты.Регистратор КАК Док,
| Обороты.СубконтоДт1 КАК Наименование,
| Обороты.СубконтоДт2 КАК Разделитель,
| ВЫБОР
| КОГДА ЕСТЬNULL(ОстаткиНаНачало.СуммаОстатокДт, 0) > 0
| ТОГДА Обороты.СуммаОборот
| ИНАЧЕ 0
| КОНЕЦ КАК Значение
|ИЗ
| РегистрБухгалтерии.Хозрасчетный.ОборотыДтКт(
| &НачалоПериода,
| &КонецПериода,
| Регистратор,
| СчетДт = &СчетДт08,
| ,
| СчетКт = &СчетКт07,
| СубконтоДт2 В (&СтатьиЗатратСписок),
| Организация = &Организация
| ) КАК Обороты
|
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрБухгалтерии.Хозрасчетный.Остатки(
| &НачалоГода,
| Счет = &СчетДт08,
| ,
| Организация = &Организация
| ) КАК ОстаткиНаНачало
| ПО Обороты.СубконтоДт1 = ОстаткиНаНачало.Субконто1
|ГДЕ
| (&НеФильтроватьПоСубконто1 ИЛИ Обороты.СубконтоДт1 = &СубконтоДт1)";
// Передаем параметры периода и организации
Запрос.УстановитьПараметр("КонецПериода", КонецДня(КонецПериода));
Запрос.УстановитьПараметр("НачалоПериода", НачалоПериода);
Запрос.УстановитьПараметр("НачалоГода", НачалоГода(НачалоПериода)); // Вычисляем начало года автоматически
Запрос.УстановитьПараметр("Организация", Организация);
// Передаем список статей затрат (избегаем хардкода внутри функции)
Запрос.УстановитьПараметр("СтатьиЗатратСписок", СтатьиЗатратСписок);
// Настраиваем гибкую фильтрацию по СубконтоДт1
ФильтроватьПоСубконто = ЗначениеЗаполнено(ДанныеФильтра);
Запрос.УстановитьПараметр("НеФильтроватьПоСубконто1", НЕ ФильтроватьПоСубконто);
Запрос.УстановитьПараметр("СубконтоДт1", ?(ФильтроватьПоСубконто, ДанныеФильтра, Неопределено));
// Заполняем счета из планов счетов напрямую
Запрос.УстановитьПараметр("СчетДт08", ПланыСчетов.Хозрасчетный.СтроительствоОбъектовОсновныхСредств);
Запрос.УстановитьПараметр("СчетКт07", ПланыСчетов.Хозрасчетный.ОборудованиеКУстановке);
РезультатЗапроса = Запрос.Выполнить();
Если РезультатЗапроса.Пустой() Тогда
Возврат Новый ТаблицаЗначений;
КонецЕсли;
ТЗ = РезультатЗапроса.Выгрузить();
ТЗ.Сортировать("Наименование");
Возврат ТЗ;
КонецФункции
Проанализируем преимущества данного решения:
ОборотыДтКт. СУБД отработает в разы быстрее на крупных базах данных.СтатьиЗатратСписок. Это позволяет динамически изменять состав отбираемых статей без необходимости переписывать программный код.&НачалоГода рассчитывается динамически с помощью системной функции НачалоГода(), что исключает ошибки ручного ввода дат пользователем.Используя данные паттерны проектирования, вы значительно повысите отказоустойчивость разрабатываемых вами конфигураций и отчетов, а также обеспечите быстродействие системы даже при многопользовательской работе в крупных информационных базах.