При разработке на платформе 1С:Предприятие одной из самых часто используемых функций встроенного языка является ЗначениеЗаполнено(). Она универсальна, удобна и безошибочно определяет, содержит ли переменная реальные данные или же она пуста (будь то пустая строка, нулевое число, пустая дата или незаполненная ссылка). Однако, когда мы переходим к написанию запросов, мы сталкиваемся с жестким ограничением: прямой функции-аналога ЗначениеЗаполнено() в языке запросов 1С не существует.
В рамках этой статьи мы подробно разберем, как решить задачу проверки заполненности полей в запросах для различных типов данных. Мы рассмотрим особенности работы с простыми типами (строки, числа, даты), научимся эффективно работать со справочниками и документами, разберем сложные случаи с составными типами данных и выясним, почему стандартные подходы могут приводить к серьезным проблемам с производительностью СУБД. Кроме того, мы проанализируем типичную ошибку начинающих разработчиков при обработке результатов запроса в коде.
Язык запросов 1С транслируется платформой в декларативный язык SQL, который выполняется непосредственно на стороне СУБД (MS SQL Server, PostgreSQL, IBM DB2 или Oracle). В базах данных понятие «пустое значение» сильно зависит от объявленного типа колонки физической таблицы. Для базы данных пустая строка, число ноль, пустая дата и значение NULL — это абсолютно разные сущности, требующие различных механизмов сравнения. Именно поэтому универсального оператора не существует, и нам приходится подбирать оптимальное решение под каждый конкретный тип данных.
Выясним, какие варианты проверок применяются для простых типов данных, и рассмотрим их особенности.
---
Если тип проверяемого реквизита заранее известен и относится к простым типам, то проверка на заполненность в секции ГДЕ реализуется достаточно просто.
1. Строковый тип данных (переменной длины)
Для строк переменной длины проверка на пустоту заключается в сравнении с пустой строкой. Посмотрим на классический пример в коде:
// Пример проверки для строки переменной длины
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| БП.Ссылка КАК БизнесПроцесс,
| БП.НомерЗаявки КАК НомерЗаявки
|ИЗ
| БизнесProcess.МойБизнесПроцесс КАК БП
|ГДЕ
| БП.НомерЗаявки <> """"";
Иногда разработчики используют альтернативный вариант проверки первого символа с помощью функции ПОДСТРОКА. Это может быть полезно в специфических сценариях СУБД для оптимизации работы с индексами:
|ГДЕ
| ПОДСТРОКА(БП.НомерЗаявки, 1, 1) <> """"
2. Строковый тип данных (фиксированной длины)
Если в метаданных для строкового реквизита задана фиксированная длина, то пустая строка в СУБД будет физически заполнена пробелами до максимальной длины поля. В этом случае обычное сравнение НомерЗаявки <> "" может вернуть некорректный результат (СУБД посчитает строку из пробелов непустой). Для обхода этой проблемы мы можем использовать шаблон проверки ПОДОБНО с отрицанием:
|ГДЕ
| НЕ БП.НомерЗаявки ПОДОБНО ""%[^ ]%""
Этот шаблон отсекает любые строки, которые не содержат никаких символов, кроме пробелов.
3. Числовой тип данных
Для чисел незаполненным значением по умолчанию считается ноль. Проверка выглядит стандартно:
|ГДЕ
| Документ.СуммаДокумента <> 0
4. Тип данных "Дата"
Пустая дата в 1С соответствует значению "0001-01-01 00:00:00". В запросе мы сравниваем поле со специальным литералом:
|ГДЕ
| Документ.ДатаОкончания <> ДАТАВРЕМЯ(1, 1, 1, 0, 0, 0)
---
Для одиночных ссылочных типов данных ситуация обстоит иначе. Просто сравнить ссылку с NULL или НЕОПРЕДЕЛЕНО нельзя, так как пустая ссылка конкретного типа — это вполне определенный объект со своим уникальным идентификатором (хоть и состоящим из одних нулей).
Для проверки заполненности ссылочного поля мы используем конструкцию ЗНАЧЕНИЕ(), передавая туда пустую ссылку соответствующего объекта метаданных:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Номенклатура
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.ОсновнойПоставщик <> ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка)";
Если поле может принимать значение NULL (например, при использовании левого соединения таблиц), проверку обязательно нужно дополнить условием ЕСТЬ NULL:
|ГДЕ
| Номенклатура.ОсновнойПоставщик <> ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка)
| И Номенклатура.ОсновнойПоставщик ЕСТЬ НЕ NULL
---
Настоящие трудности начинаются тогда, когда реквизит имеет составной тип данных (например, поле может содержать ссылку на справочник "Контрагенты", справочник "ФизическиеЛица" или быть незаполненным). В этом случае в поле может храниться:
Неопределено (когда тип вообще не выбран);NULL (если поле получено в результате левого соединения).Рассмотрим популярные, но не всегда оптимальные методы решения этой задачи, а также разберем "лучшую практику" (Best Practice) с точки зрения производительности.
Поле.Ссылка ЕСТЬ NULL)Часто в коде можно встретить следующую конструкцию проверки заполненности составного поля:
|ГДЕ
| ПолеСоставногоТипа.Ссылка ЕСТЬ NULL
Внимание! Использование обращения через точку к реквизитам составного типа (включая предопределенное поле Ссылка) крайне негативно сказывается на производительности СУБД. Платформа 1С для трансляции этого обращения генерирует внутренний SQL-запрос с оператором LEFT JOIN ко всем физическим таблицам, входящим в этот составной тип.
Если составной тип включает 15-20 различных документов, СУБД выполнит левое соединение со всеми 20 таблицами ради одной лишь проверки на заполненность. Это приводит к колоссальным нагрузкам на сервер, сканированию индексов (Index/Table Scan) и может полностью заблокировать работу базы данных в нагруженных системах.
Наиболее быстрым и производительным способом проверки заполненности составного поля является передача заранее подготовленного массива пустых значений через параметры запроса. Этот метод позволяет СУБД выполнить простую и быструю проверку без сложных соединений таблиц.
Разберем этот алгоритм по шагам на встроенном языке 1С:
// Шаг 1. Создаем массив, куда поместим все возможные пустые значения реквизита
МассивПустыхЗначений = Новый Массив;
МассивПустыхЗначений.Добавить(Неопределено);
// Получаем описание типов нашего составного поля (например, реквизит "Субъект" документа)
ОписаниеТиповСоставногоПоля = Метаданные.Документы.МойДокумент.Реквизиты.Субъект.Тип;
// Проходим по всем типам составного поля и добавляем их пустые ссылки в массив
Для Каждого ТекущийТип Из ОписаниеТиповСоставногоПоля.Типы() Цикл
// Исключаем простые типы (числа, строки, даты), если они вдруг есть в составе
Если ТекущийТип = Тип("Строка") Или ТекущийТип = Тип("Число")
Или ТекущийТип = Тип("Дата") Или ТекущийТип = Тип("Булево") Тогда
Продолжить;
КонецЕсли;
// Получаем пустую ссылку для текущего типа объекта
ПустаяСсылкаОбъекта = Новый(ТекущийТип);
МассивПустыхЗначений.Добавить(ПустаяСсылкаОбъекта);
КонецЦикла;
// Шаг 2. Передаем массив в запрос
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Док.Ссылка КАК Документ,
| Док.Субъект КАК Субъект
|ИЗ
| Документ.МойДокумент КАК Док
|ГДЕ
| НЕ Док.Субъект В (&МассивПустыхЗначений)
| И Док.Субъект ЕСТЬ НЕ NULL";
Запрос.УстановитьПараметр("МассивПустыхЗначений", МассивПустыхЗначений);
РезультатЗапроса = Запрос.Выполнить();
Такой подход работает максимально эффективно, так как СУБД выполняет быструю индексированную проверку на вхождение значения в переданный список констант, не прибегая к дорогостоящим соединениям таблиц базы данных.
---
Важно помнить, что если вы разрабатываете отчет или динамический список с использованием Системы компоновки данных (СКД), то платформа берет оптимизацию запросов на себя. В выражениях вычисляемых полей, пользовательских полях, а также в настройках отборов на вкладке «Настройки» вы можете напрямую использовать функцию ЗНАЧЕНИЕЗАПОЛНЕНО(<Выражение>) (или её англоязычный аналог VALUEISFILLED).
При генерации итогового макета компоновки данных СКД самостоятельно проанализирует метаданные полей и преобразует эту функцию в оптимальный SQL-код с учетом всех особенностей типов данных.
---
Рассмотрим еще одну распространенную проблему, с которой часто сталкиваются начинающие разработчики при работе с запросами на встроенном языке. Проанализируем типичный сценарий: программист написал правильный запрос, но при попытке получить данные в коде видит пустой результат.
Посмотрим на ошибочный фрагмент кода:
МойЗапрос = Новый Запрос;
МойЗапрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 300
| БП.НомерЗаявки КАК НомерЗаявки
|ИЗ
| БизнесПроцесс.МойБизнесПроцесс КАК БП";
РезультатЗапроса = МойЗапрос.Выполнить();
// Ошибка: Попытка увидеть строки в объекте РезультатЗапроса или Выборка без позиционирования
Выясним причину проблемы. При просмотре переменных РезультатЗапроса или полученного из него объекта ВыборкаИзРезультатаЗапроса во встроенном отладчике 1С разработчик видит лишь структуру колонок, но не видит самих строк данных. Это совершенно нормально и обусловлено архитектурой работы платформы с СУБД.
Объект РезультатЗапроса — это лишь указатель на подготовленный сервером набор данных. Для того чтобы физически прочитать эти данные в коде, мы должны использовать один из двух подходов:
Подход 1. Обход выборки в цикле с помощью метода Следующий()
Для последовательного чтения записей из базы данных без избыточного расхода оперативной памяти сервера приложений используется выборка. Метод Следующий() позиционирует указатель на очередную строку данных. Если строка успешно прочитана, метод возвращает Истина, если строки закончились — Ложь.
РезультатЗапроса = МойЗапрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
// Обрабатываем полученные данные
ИмяНомера = Выборка.НомерЗаявки;
Сообщить(ИмяНомера);
КонецЦикла;
Подход 2. Выгрузка результата в Таблицу значений
Если вам необходимо получить весь объем данных сразу в виде таблицы в оперативной памяти для дальнейшей сложной обработки (сортировка, поиск, передача в другие процедуры), используйте метод Выгрузить():
РезультатЗапроса = МойЗапрос.Выполнить();
ТаблицаРезультатов = РезультатЗапроса.Выгрузить();
// Теперь мы можем узнать точное количество строк и обратиться к ним по индексу
КоличествоСтрок = ТаблицаРезультатов.Количество();
Сообщить("Получено строк: " + КоличествоСтрок);
---
Мы подробно разобрали, как компенсировать отсутствие функции ЗначениеЗаполнено() в запросах 1С. Закрепим основные правила:
"", 0, ДАТАВРЕМЯ(1,1,1)).ЗНАЧЕНИЕ(Справочник.Имя.ПустаяСсылка)..Ссылка ЕСТЬ NULL). Самым производительным решением является передача массива пустых ссылок в качестве параметра запроса.ЗНАЧЕНИЕЗАПОЛНЕНО().Следующий() для обхода выборки или метод Выгрузить() для получения таблицы значений.