При разработке отчетов и обработок в среде 1С:Предприятие разработчики часто сталкиваются с задачей исключения определенных видов расчетов (например, разовых премий) из выборки запроса. Казалось бы, задача простая, но попытка решить её "в лоб" через сравнение наименований или неаккуратное использование ссылочных типов приводит к снижению производительности, ошибкам выполнения и трудностям при дальнейшей поддержке кода. В этой статье мы подробно разберем, почему возникают подобные проблемы, как исправить ошибку сравнения типов и какие архитектурные подходы помогут сделать ваш код надежным и масштабируемым.
Рассмотрим типичную ошибку, с которой сталкиваются разработчики при написании запросов. В исходном варианте условие фильтрации выглядело следующим образом:
И СведенияОДоходахНДФЛ.Начисление.Наименование <> &Начисление
При этом в качестве параметра &Начисление передавался массив ссылок, полученных с помощью метода НайтиПоНаименованию. При выполнении такого запроса платформа выдает ошибку: “Неверные параметры в операции сравнения. Нельзя сравнивать поля неограниченной длины и поля несовместимых типов”.
Выясним причину этой ошибки по шагам:
Наименование является строковым типом данных, в то время как параметр &Начисление содержит массив ссылок на элементы плана видов расчета. Сравнивать строку со ссылкой напрямую (и тем более с массивом ссылок) в запросах 1С категорически запрещено.<>, =, >, <) для полей неограниченной длины в условиях ГДЕ не поддерживается СУБД напрямую и вызывает ошибку.В (или НЕ В для исключения), но никак не операторы равенства или неравенства.Для того чтобы исправить ошибку несовместимости типов, мы должны сравнивать непосредственно ссылочные поля и использовать оператор множественного сравнения. Проанализируем правильный вариант условия в запросе:
И НЕ СведенияОДоходахНДФЛ.Начисление В (&Начисление)
Обратите внимание на важную деталь, которой часто пренебрегают разработчики. Иногда в коде можно встретить конструкцию вида СведенияОДоходахНДФЛ.Начисление.Ссылка. Использование обращения через точку к реквизиту Ссылка у ссылочного поля является грубой ошибкой разыменования.
Разберем подробнее, почему обращение Начисление.Ссылка ухудшает производительность:
Поле Начисление в регистре накопления уже само по себе хранит ссылку на элемент плана видов расчета. Когда мы пишем .Ссылка, платформа 1С при трансляции запроса на уровень СУБД (SQL) вынуждена создавать неявное левое соединение (Implicit Left Join) с таблицей плана видов расчета только ради того, чтобы получить то же самое значение ссылки, которое и так уже содержится в исходной таблице регистра. Это приводит к лишней нагрузке на СУБД и замедлению выполнения запроса, особенно на больших базах данных. Поэтому всегда пишите просто Начисление без обращения к реквизиту Ссылка.
При заполнении параметров запроса разработчики часто сталкиваются с проблемой поиска элементов, в названии которых присутствуют кавычки. Например, начисление называется: Премия разовая "За особые заслуги".
Посмотрим на пример правильного экранирования кавычек во встроенном языке 1С. Чтобы вставить двойную кавычку внутрь строкового литерала, её необходимо продублировать. Кроме того, вся строка должна быть заключена в кавычки. Правильный литерал для кода будет выглядеть следующим образом:
Начисление.Добавить(ПланыВидовРасчета.Начисления.НайтиПоНаименованию("Премия разовая ""За особые заслуги"""));
Если вы случайно поставите лишние кавычки на конце строки (например, четыре кавычки вместо трех), в отладчике вы увидите искаженное наименование с лишней кавычкой на конце, из-за чего метод поиска вернет пустую ссылку.
Рассмотрим еще одну критическую уязвимость, которая может приводить к непредсказуемому поведению программы. По умолчанию метод НайтиПоНаименованию(<Наименование>, <ТочноеСоответствие>) использует значение Ложь для второго параметра.
Это означает, что поиск выполняется по началу строки (по префиксу). Если в вашей информационной базе существуют начисления "Премия разовая" и "Премия разовая ""За особые заслуги""", то вызов метода без указания второго параметра для первой премии:
ПланыВидовРасчета.Начисления.НайтиПоНаименованию("Премия разовая")
с большой вероятностью вернет ссылку на вторую премию (за особые заслуги), если она окажется первой в индексе базы данных. Чтобы избежать этой проблемы, всегда явно передавайте параметр точного соответствия:
ПланыВидовРасчета.Начисления.НайтиПоНаименованию("Премия разовая", Истина)
Поиск элементов по наименованию в коде и последующая передача их в запрос (так называемый "хардкод") — это архитектурное решение, которое крайне не рекомендуется использовать в промышленной разработке. Если бухгалтер переименует начисление в режиме "1С:Предприятие" (например, исправит опечатку), поиск вернет пустую ссылку, запрос перестанет исключать эту премию, и расчеты НДФЛ исказятся без каких-либо видимых ошибок системы.
Рассмотрим три надежных альтернативных решения, которые сделают вашу систему гибкой и устойчивой.
Если вы имеете доступ к конфигурированию системы и начисления являются ключевыми для логики учета, наилучшим решением будет сделать их предопределенными в плане видов расчета Начисления. В этом случае мы полностью избавляемся от поиска по строке и можем обращаться к элементам напрямую через точку:
Начисление = Новый Массив;
Начисление.Добавить(ПланыВидовРасчета.Начисления.ПремияРазовая);
Начисление.Добавить(ПланыВидовРасчета.Начисления.ПремияЗаОсобыеЗаслуги);
Запрос.УстановитьПараметр("Начисление", Начисление);
Даже если пользователи изменят наименование этих начислений в пользовательском интерфейсе, их внутренний идентификатор останется прежним, и программная логика не нарушится.
Если состав исключаемых премий может меняться со временем, хардкод в конфигураторе заставит вас переписывать код при каждом изменении бизнес-логики. Чтобы этого избежать, добавим в план видов расчета Начисления новый реквизит типа Булево, например, ИсключатьИзРасчетаНДФЛ.
Тогда условие в нашем запросе примет максимально простой и быстродействующий вид:
И НЕ СведенияОДоходахНДФЛ.Начисление.ИсключатьИзРасчетаНДФЛ
Теперь пользователю или администратору достаточно будет зайти в карточку начисления и установить соответствующий флаг. Никаких доработок кода при добавлении новых видов премий больше не потребуется.
Если вы не хотите менять структуру плана видов расчета, можно создать простой регистр сведений (например, ИсключаемыеНачисленияИзНДФЛ) с одним измерением Начисление (тип ПланВидовРасчетаСсылка.Начисления).
В запросе мы сможем выполнить левое соединение с этим регистром сведений:
ВЫБРАТЬ
СведенияОДоходахНДФЛ.ФизическоеЛицо КАК ФизическоеЛицо,
СУММА(СведенияОДоходахНДФЛ.СуммаДохода) КАК СуммаДохода
ИЗ
РегистрНакопления.СведенияОДоходахНДФЛ КАК СведенияОДоходахНДФЛ
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ИсключаемыеНачисленияИзНДФЛ КАК Исключения
ПО СведенияОДоходахНДФЛ.Начисление = Исключения.Начисление
ГДЕ
СведенияОДоходахНДФЛ.МесяцНалоговогоПериода >= &ДатаНач
И СведенияОДоходахНДФЛ.МесяцНалоговогоПериода <= &ДатаКон
И СведенияОДоходахНДФЛ.ФизическоеЛицо = &ФизическоеЛицо
И Исключения.Начисление ЕСТЬ NULL
Такой подход обеспечивает максимальную гибкость решения: пользователи сами управляют списком исключений через специальный интерфейс настройки в режиме "1С:Предприятие", а сам запрос работает оптимально на уровне СУБД.