ЕСТЬNULL против ВЫБОР: можно ли обойтись без функции ISNULL в запросе 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В практике разработки на платформе 1С:Предприятие часто возникают споры о стилистике и оптимизации написания запросов. Один из фундаментальных вопросов касается обработки NULL значений, которые неизбежно появляются при левых, правых и полных соединениях таблиц. Рассмотрим подробно ситуацию, предложенную автором темы: можно ли полностью отказаться от использования функции ЕСТЬNULL (ISNULL) и заменить ее конструкцией ВЫБОР (CASE)? Разберем технические последствия такого решения, взглянем на то, как это работает на уровне СУБД, и выясним, какой подход является профессиональным стандартом.

Постановка задачи

Представим классическую задачу: нам необходимо построить отчет, объединяющий данные из двух таблиц — например, ТаблицаЗакупки и ТаблицаВыплаты. Мы хотим видеть всех контрагентов, по которым были либо закупки, либо выплаты. Для этого используется ПОЛНОЕ СОЕДИНЕНИЕ (FULL JOIN). При таком соединении, если контрагент есть в закупках, но нет в выплатах, поля таблицы выплат будут содержать NULL. И наоборот. Чтобы вывести единое поле "Контрагент", нам нужно выбрать непустое значение из двух таблиц. Рассмотрим два варианта синтаксиса, которые решают эту задачу. Вариант 1: Использование функции ЕСТЬNULL


ВЫБРАТЬ
    ЕСТЬNULL(ТаблицаВыплаты.Контрагент, ТаблицаЗакупки.Контрагент) КАК Контрагент
ИЗ
    ТаблицаВыплаты КАК ТаблицаВыплаты
        ПОЛНОЕ СОЕДИНЕНИЕ ТаблицаЗакупки КАК ТаблицаЗакупки
        ПО ТаблицаВыплаты.Контрагент = ТаблицаЗакупки.Контрагент

Вариант 2: Использование конструкции ВЫБОР


ВЫБРАТЬ
    ВЫБОР
        КОГДА ТаблицаЗакупки.Контрагент ЕСТЬ NULL
            ТОГДА ТаблицаВыплаты.Контрагент
        ИНАЧЕ ТаблицаЗакупки.Контрагент
    КОНЕЦ КАК Контрагент
ИЗ
    ТаблицаВыплаты КАК ТаблицаВыплаты
        ПОЛНОЕ СОЕДИНЕНИЕ ТаблицаЗакупки КАК ТаблицаЗакупки
        ПО ТаблицаВыплаты.Контрагент = ТаблицаЗакупки.Контрагент

На первый взгляд, оба варианта делают одно и то же. Логически это действительно так. Однако дьявол кроется в деталях реализации, производительности и читаемости кода. Проанализируем эти аспекты глубже.

Сравнение читаемости и компактности кода

Первый и самый очевидный аргумент в пользу ЕСТЬNULL — это лаконичность. 1. Компактность: Запись ЕСТЬNULL(Поле1, Поле2) занимает одну строку и сразу дает понять намерение разработчика: "Возьми первое, если пусто — возьми второе". Конструкция ВЫБОР требует написания условия, ветки "ТОГДА", ветки "ИНАЧЕ" и закрывающего слова "КОНЕЦ". Это загромождает запрос, особенно если таких полей в выборке много. 2. Семантика: Функция ЕСТЬNULL специально создана для обработки отсутствующих значений. Использование ВЫБОР для этой цели похоже на стрельбу из пушки по воробьям — инструмент слишком мощный и универсальный для такой тривиальной задачи. Если вы работаете в команде, использование ЕСТЬNULL является стандартом де-факто. Код с ВЫБОР для замены NULL будет вызывать вопросы на этапе Code Review.

Производительность и трансляция в SQL

Теперь перейдем к самому важному техническому аспекту — как платформа 1С транслирует эти конструкции в SQL-запрос к базе данных (MS SQL, PostgreSQL и др.).

Как работает ЕСТЬNULL (ISNULL / COALESCE)

Когда вы пишете ЕСТЬNULL, платформа 1С преобразует это в нативную функцию СУБД. * В MS SQL Server это чаще всего функция ISNULL. * В PostgreSQL это функция COALESCE. Эти функции высоко оптимизированы внутри движка базы данных. Они предназначены для максимально быстрой проверки значения.

Как работает ВЫБОР (CASE)

Конструкция ВЫБОР транслируется в SQL-оператор CASE WHEN ... THEN ... ELSE ... END. И здесь кроется потенциальная проблема с производительностью, особенно если проверяемое выражение — это не просто поле таблицы, а сложная функция или вложенный запрос. Рассмотрим пример с вычислением агрегатной функции:


// Плохой вариант с ВЫБОР
ВЫБОР
    КОГДА СУММА(Продажи.Сумма) ЕСТЬ NULL ТОГДА 0
    ИНАЧЕ СУММА(Продажи.Сумма)
КОНЕЦ

В некоторых случаях (в зависимости от оптимизатора СУБД) такой запрос может привести к двойному вычислению агрегатной функции СУММА(Продажи.Сумма): один раз для проверки условия IS NULL, и второй раз для возврата значения в ветке ELSE. Сравним с вариантом через ЕСТЬNULL:


// Хороший вариант
ЕСТЬNULL(СУММА(Продажи.Сумма), 0)

В этом случае СУБД гарантированно вычисляет значение один раз, и если оно равно NULL, подставляет 0. Это критически важно при работе с большими объемами данных или сложными математическими вычислениями внутри запроса.

Особенности типизации данных

Еще один неочевидный момент, который стоит учитывать — это приведение типов. При использовании функции ЕСТЬNULL(Поле1, Поле2), платформа 1С и СУБД пытаются привести результат к наиболее "широкому" типу данных, который может вместить оба значения. * Если Поле1 — это Строка(10), а Поле2 — Строка(100), результатом будет Строка(100). Однако в чистом SQL (в частности, T-SQL MS SQL Server) функция ISNULL имеет особенность: она возвращает тип данных первого аргумента. Если первое поле короче второго, данные второго поля могут быть обрезаны. Платформа 1С обычно нивелирует эти риски, корректно формируя запрос, но использование конструкции ВЫБОР дает разработчику явный контроль над типами через функцию ВЫРАЗИТЬ. Тем не менее, для простых задач замены NULL на значение по умолчанию (например, 0 или пустую строку), ЕСТЬNULL работает предсказуемо и корректно.

Ограничение функции ЕСТЬNULL в 1С

Важно отметить функциональное отличие реализации в 1С от стандарта SQL. В языке запросов 1С функция ЕСТЬNULL принимает строго два параметра. ЕСТЬNULL(ВыражениеПроверки, ЗаменаЕслиNull) В то же время в стандарте SQL функция COALESCE может принимать неограниченное количество аргументов и возвращает первый не NULL. Если вам нужно реализовать логику "Взять Поле1, если пусто — то Поле2, если и оно пусто — то Поле3", в 1С придется строить "лесенку": ЕСТЬNULL(Поле1, ЕСТЬNULL(Поле2, Поле3)) В этом конкретном случае (когда нужно перебрать 3 и более полей), конструкция ВЫБОР может выглядеть более читаемой, так как позволяет записать логику линейно:


ВЫБОР
    КОГДА Поле1 ЕСТЬ НЕ NULL ТОГДА Поле1
    КОГДА Поле2 ЕСТЬ НЕ NULL ТОГДА Поле2
    ИНАЧЕ Поле3
КОНЕЦ

Альтернативный архитектурный подход: ОБЪЕДИНЕНИЕ вместо ПОЛНОГО СОЕДИНЕНИЯ

В обсуждении проблемы была высказана еще одна важная мысль: использование ЕСТЬNULL часто является следствием использования ПОЛНОЕ СОЕДИНЕНИЕ (Full Join). С точки зрения производительности СУБД, полное соединение — одна из самых тяжелых операций, так как она плохо использует индексы. Профессиональным приемом оптимизации является замена полного соединения на конструкцию ОБЪЕДИНЕНИЕ ВСЕ (Union All). Вместо того чтобы соединять таблицы и потом пытаться "склеить" поля через ЕСТЬNULL, мы можем выбрать данные двумя запросами и объединить их. Пример оптимизации через ОБЪЕДИНЕНИЕ:


// Выбираем данные из первой таблицы и присоединяем вторую (Левое соединение)
ВЫБРАТЬ
    ТаблицаЗакупки.Контрагент КАК Контрагент,
    ТаблицаЗакупки.Сумма,
    ЕСТЬNULL(ТаблицаВыплаты.Сумма, 0) КАК СуммаВыплаты
ИЗ
    ТаблицаЗакупки КАК ТаблицаЗакупки
        ЛЕВОЕ СОЕДИНЕНИЕ ТаблицаВыплаты КАК ТаблицаВыплаты
        ПО ТаблицаЗакупки.Контрагент = ТаблицаВыплаты.Контрагент

ОБЪЕДИНЕНИЕ ВСЕ

// Выбираем данные из второй таблицы, которых НЕТ в первой
ВЫБРАТЬ
    ТаблицаВыплаты.Контрагент,
    0, // Суммы закупки нет
    ТаблицаВыплаты.Сумма
ИЗ
    ТаблицаВыплаты КАК ТаблицаВыплаты
        ЛЕВОЕ СОЕДИНЕНИЕ ТаблицаЗакупки КАК ТаблицаЗакупки
        ПО ТаблицаВыплаты.Контрагент = ТаблицаЗакупки.Контрагент
ГДЕ
    ТаблицаЗакупки.Контрагент ЕСТЬ NULL

Хотя код стал объемнее, такой запрос часто выполняется быстрее на больших объемах данных и позволяет избавиться от ЕСТЬNULL для ключевых полей группировки, так как мы явно выбираем Контрагента из той таблицы, где он точно есть.

Выводы и рекомендации

Подводя итог дискуссии о необходимости ЕСТЬNULL, сформулируем основные правила: 1. Используйте ЕСТЬNULL по умолчанию. Если вам нужно заменить NULL на значение по умолчанию (0, "", Дата(1,1,1)) или выбрать одно из двух полей — всегда используйте ЕСТЬNULL. Это компактнее, понятнее другим программистам и безопаснее с точки зрения производительности. 2. Избегайте ВЫБОР для замены NULL. Использование CASE (ВЫБОР) для простой проверки на NULL считается плохим тоном и может привести к замедлению запроса из-за дублирования вычислений выражений. 3. Используйте ВЫБОР для сложной логики. Если логика выбора значения зависит от нескольких условий или нужно перебрать более двух полей подряд — тогда ВЫБОР оправдан. 4. Оптимизируйте структуру запроса. Если вы видите в запросе множество ЕСТЬNULL над полями соединения, подумайте, можно ли переписать запрос через ОБЪЕДИНЕНИЕ ВСЕ. Это может существенно ускорить выполнение отчета.

Таким образом, хотя технически обойтись без ЕСТЬNULL можно, делать этого не следует. Эта функция — важный инструмент в арсенале разработчика 1С, обеспечивающий баланс между краткостью кода и эффективностью его исполнения СУБД.

← На главную