При разработке и поддержке отчетов на платформе 1С:Предприятие 7.7 программисты часто сталкиваются с необходимостью отсечь «неактивных» физических лиц или сотрудников. Это могут быть элементы справочника, помеченные на удаление, или сотрудники, которые уже уволены на момент формирования отчета (например, при расчете потребности в спецодежде, анализе взаиморасчетов или выдаче инвентаря).
В этой статье мы подробно разберем, почему стандартные попытки отфильтровать данные через промежуточную Таблицу Значений непосредственно в запросе приводят к ошибкам, как правильно использовать Список Значений для фильтрации, а также рассмотрим альтернативные, более изящные и производительные способы решения этой задачи в среде 1С 7.7.
Рассмотрим ситуацию, с которой часто сталкиваются разработчики. Нам необходимо построить отчет по потребностям, основываясь на документах выдачи. При этом в отчет не должны попадать документы, оформленные на физических лиц, которые помечены на удаление или уволены.
Типичная, но ошибочная логика реализации выглядит следующим образом:
// ОШИБОЧНЫЙ КОД! Так делать нельзя:
|Условие(ФизЛицо в ТЗФизЛица.ФизЛицо);
Выясним причину, почему этот код не работает. В языке запросов 1С 7.7 обращение к колонке Таблицы Значений вида ТЗФизЛица.ФизЛицо внутри условия запроса воспринимается платформой как обращение к единственной конкретной переменной. На момент выполнения запроса эта переменная будет содержать значение из текущей (активной) строки Таблицы Значений. Если обход строк таблицы предварительно не выполнялся, текущая строка будет неопределенной (или это будет последняя обработанная строка из предыдущего цикла).
В результате запрос будет проверять всех сотрудников на равенство одному-единственному конкретному физическому лицу из ТЗ, из-за чего отчет либо вернет пустой результат, либо покажет некорректные данные.
Для того чтобы условие фильтрации по списку элементов отработало корректно, нам необходимо использовать специальный объект платформы — СписокЗначений. Запрос 1С 7.7 умеет корректно обрабатывать оператор в, когда в качестве правого операнда выступает именно заполненный список значений.
Разберем по шагам правильный алгоритм реализации:
Шаг 1. Получение исходных данных в Таблицу Значений
Сначала мы собираем нужных нам физических лиц (без пометки на удаление) с помощью первого запроса и выгружаем их в Таблицу Значений.
ТЗФизЛица = ""; // Инициализируем переменную как пустую строку для выгрузки запроса
Запрос1 = СоздатьОбъект("Запрос");
ТекстЗапроса1 =
"//{{ЗАПРОС(АктивныеФизЛица)
|Без итогов;
|ФизЛицо = Справочник.ФизическиеЛица.ТекущийЭлемент;
|Группировка ФизЛицо Без групп Без упорядочивания;
|Условие(ФизЛицо.ПометкаУдаления() = 0);
|//}}ЗАПРОС";
Если Запрос1.Выполнить(ТекстЗапроса1) = 0 Тогда
Возврат;
КонецЕсли;
// Выгружаем результаты первого запроса напрямую в ТЗ
Запрос1.Выгрузить(ТЗФизЛица, 0, 0);
Шаг 2. Преобразование колонки ТЗ в Список Значений
Для передачи списка во второй запрос нам необходимо выгрузить конкретную колонку из нашей ТЗ в объект СписокЗначений. Используем метод Выгрузить() объекта ТаблицаЗначений:
СЗФизЛица = СоздатьОбъект("СписокЗначений");
// Выгружаем только колонку "ФизЛицо" из ТЗ в наш Список Значений
ТЗФизЛица.Выгрузить(СЗФизЛица, , , "ФизЛицо");
Шаг 3. Использование Списка Значений во втором запросе
Теперь мы можем безопасно использовать полученный список в условии второго запроса с помощью оператора в:
Запрос2 = СоздатьОбъект("Запрос");
ТекстЗапроса2 =
"//{{ЗАПРОС(ПотребностьВыдача)
|Без итогов;
|Док = Документ.Выдача.ТекущийДокумент;
|ФизЛицо = Документ.Выдача.ФизЛицо;
|Количество = Документ.Выдача.Количество;
|Группировка ФизЛицо Без групп Без упорядочивания;
|Группировка Док;
|Условие(ФизЛицо в СЗФизЛица); // Фильтрация по нашему Списку Значений
|//}}ЗАПРОС";
Если Запрос2.Выполнить(ТекстЗапроса2) = 1 Тогда
// Обработка результатов запроса и вывод в отчет
КонецЕсли;
Использование двух запросов и промежуточной выгрузки в ТЗ/СЗ — рабочее решение, но с точки зрения архитектуры и производительности оно не является оптимальным. Если нам нужно всего лишь отсечь физических лиц, помеченных на удаление, мы можем сделать это напрямую в основном запросе, используя обращение к реквизитам через точку.
Рассмотрим, как упростить код и выполнить задачу в один шаг:
Запрос = СоздатьОбъект("Запрос");
ТекстЗапроса =
"//{{ЗАПРОС(ПотребностьБезУдаленных)
|Без итогов;
|Док = Документ.Выдача.ТекущийДокумент;
|ФизЛицо = Документ.Выдача.ФизЛицо;
|Количество = Документ.Выдача.Количество;
|Группировка ФизЛицо Без групп Без упорядочивания;
|Группировка Док;
|Условие(ФизЛицо.ПометкаУдаления() = 0); // Прямая проверка пометки на удаление
|//}}ЗАПРОС";
Если Запрос.Выполнить(ТекстЗапроса) = 1 Тогда
// Выводим результат в отчет
КонецЕсли;
Важный синтаксический нюанс: Обратите внимание на написание метода ПометкаУдаления(). Начинающие разработчики 1С 7.7 часто допускают ошибку, записывая его как ПометкаУдаление(). Правильный синтаксис — именно с буквой «я» на конце: ПометкаУдаления(). При неверном написании система выдаст синтаксическую ошибку на этапе компиляции или выполнения запроса.
В кадровых конфигурациях 1С 7.7 (таких как «Зарплата и Кадры 7.7») увольнение сотрудника не приводит к автоматической пометке его карточки на удаление. Это логично, так как по сотруднику в базе должны сохраняться исторические данные для сдачи отчетности и расчета налогов. Поэтому проверка ПометкаУдаления() = 0 не поможет убрать уволенных из отчета по спецодежде.
Чтобы решить эту проблему, нам нужно проверять периодический реквизит состояния сотрудника на дату формирования отчета. Проанализируем два варианта решения этой задачи.
Если структура конфигурации позволяет обращаться к состоянию сотрудника непосредственно через запрос (когда состояние является реквизитом справочника Сотрудники), мы можем написать следующее условие:
|Сотр = Справочник.Сотрудники.ТекущийЭлемент;
|Состояние = Справочник.Сотрудники.Состояние;
|...
|Условие(Состояние <> Перечисление.СостоянияФизЛиц.Уволен);
Если получить состояние напрямую в запросе сложно или требуется комплексная проверка (например, по кадровым документам), мы можем сначала программно собрать список уволенных сотрудников в СписокЗначений, а затем исключить их в запросе.
Давайте посмотрим на особенности реализации отрицания в условиях запросов 1С 7.7. Для исключения элементов из списка используется конструкция с двойными скобками:
|Условие(НЕ(ФизЛицо в СписокУволенных));
Существует также альтернативный метод проверки через функцию списка значений Принадлежит():
|Условие(СписокУволенных.Принадлежит(ФизЛицо) = 0);
Важное предупреждение о производительности на SQL-версиях баз данных: Конструкция НЕ(ФизЛицо в СписокУволенных) является более предпочтительной. При работе на SQL-версии 1С 7.7 эта конструкция транслируется движком платформы в эффективный SQL-запрос с оператором WHERE ... NOT IN (...), который выполняется непосредственно на сервере базы данных. Использование метода Принадлежит() заставит платформу выгружать все записи на локальный компьютер пользователя и фильтровать их силами клиентского приложения, что приведет к критическому падению скорости работы отчета на больших базах.
Разберем скрытую проблему, о которой должен знать каждый разработчик 1С 7.7. Предположим, вы написали условие фильтрации по списку значений:
|Условие(ФизЛицо в СписокРазрешенных);
Если на момент выполнения запроса список СписокРазрешенных окажется абсолютно пустым (например, ни одно физлицо не подошло под критерии предварительного отбора), то, согласно внутренней логике движка запросов 1С 7.7, данное условие вернет значение Истина для всех элементов базы! То есть запрос вернет вообще всех сотрудников, проигнорировав фильтр.
Чтобы избежать этой серьезной логической ошибки, мы рекомендуем формировать текст запроса динамически, проверяя размер списка перед его выполнением:
ТекстЗапроса =
"//{{ЗАПРОС(Потребность)
|Без итогов;
|ФизЛицо = Документ.Выдача.ФизЛицо;
|Количество = Документ.Выдача.Количество;
|Группировка ФизЛицо Без групп Без упорядочивания;";
// Проверяем, есть ли элементы в списке
Если СписокРазрешенных.РазмерСписка() > 0 Тогда
ТекстЗапроса = ТекстЗапроса + РазделительСтрок + "Условие(ФизЛицо в СписокРазрешенных);";
Иначе
// Если список пуст, мы можем принудительно добавить заведомо ложное условие,
// чтобы запрос вернул пустой результат, либо прервать выполнение.
ТекстЗапроса = ТекстЗапроса + РазделительСтрок + "Условие(ФизЛицо = 0);";
КонецЕсли;
ТекстЗапроса = ТекстЗапроса + РазделительСтрок + "//}}ЗАПРОС";
Для того чтобы ваши отчеты выполнялись максимально быстро и не нагружали оперативную память сервера, придерживайтесь следующих правил при написании запросов:
Без итогов: Если вам не требуются промежуточные или общие итоги по группировкам, всегда указывайте эту директиву в начале запроса. Это значительно ускоряет выборку данных.Без упорядочивания в группировках: По умолчанию 1С 7.7 пытается отсортировать результаты группировки (например, по алфавиту или коду). Если порядок вывода строк для алгоритма не важен (например, при выгрузке данных в ТЗ для последующей обработки), добавляйте Без упорядочивания к каждой группировке. Это исключает тяжелую операцию сортировки на стороне СУБД.Без групп для справочников: При группировке по иерархическим справочникам (таким как ФизическиеЛица или Сотрудники) в выборку по умолчанию попадают родительские группы. Чтобы выгрузить плоский и чистый список элементов без пустых строк-родителей, используйте конструкцию:
|Группировка ФизЛицо Без групп;
Это избавит вас от необходимости писать циклы для очистки Таблицы Значений от пустых строк.
Применяя данные подходы, мы получаем надежный, быстродействующий и легко масштабируемый код отчетов, который корректно обрабатывает состояния увольнения и удаления сотрудников на платформе 1С 7.7.