Почему пропадают записи в запросе 1С при обращении к реквизитам через точку и как это исправить?

Программист 1С v8.3 (Обычные формы)
← На главную

В практике разработки на платформе 1С:Предприятие одной из самых распространенных «ловушек» для начинающих программистов является использование обращений к реквизитам ссылочных полей через точку (разыменование ссылок) непосредственно в секции ГДЕ (WHERE) текста запроса. Это приводит к неожиданному «исчезновению» строк из итоговой выборки, если ссылка пустая. Рассмотрим подробно, почему так происходит и как правильно составить запрос, чтобы данные не терялись.

Суть проблемы: механизм неявного внутреннего соединения

Проанализируем ситуацию, представленную в вопросе автора. В тексте запроса используются условия вида Пользователи.Ответственный.Напарник.Код <> 11. На первый взгляд кажется, что система просто должна проверить код напарника. Однако на уровне СУБД (SQL) платформа 1С преобразует это обращение через точку в внутреннее соединение (INNER JOIN).

Разберем по шагам, что происходит внутри системы:

  1. Когда вы пишете Пользователи.Ответственный, система соединяет таблицу пользователей с таблицей сотрудников (или кто является типом данного поля).
  2. Когда вы добавляете еще одну точку — Ответственный.Напарник, система выполняет еще одно соединение с таблицей пользователей.
  3. Если в секции ГДЕ стоит условие на конечное поле этой цепочки, платформа использует INNER JOIN для всей цепочки соединений.

Важный момент: Если у основного пользователя поле Напарник не заполнено (содержит пустую ссылку), то внутреннее соединение с таблицей напарников не найдет соответствующей записи. В результате вся строка основного пользователя будет исключена из результата запроса, даже если сам пользователь полностью соответствует остальным критериям фильтрации. Это и есть причина того, почему «пользователь без напарника исчезает».

Способ 1. Использование функции ЕСТЬNULL

Самый быстрый способ «вылечить» такой запрос без радикальной переработки его структуры — использование функции ЕСТЬNULL. Эта функция позволяет подставить альтернативное значение в случае, если результат обращения через точку возвращает NULL (что и происходит, когда ссылка пуста).

Проанализируем, как изменится условие. Вместо прямого сравнения мы обернем обращение к коду:


|ГДЕ
|    ЕСТЬNULL(Пользователи.Ответственный.Напарник.Код, 0) <> 11

В данном случае, если у ответственного нет напарника, функция ЕСТЬNULL вернет 0. Поскольку 0 <> 11 — это ИСТИНА, строка с пользователем попадет в результат. Однако стоит помнить, что такой подход может негативно сказаться на производительности (индексы по полю Код могут не использоваться), поэтому для больших таблиц лучше применять другие методы.

Способ 2. Явное левое соединение

Наиболее правильным и «прозрачным» с точки зрения SQL методом является отказ от обращений через точку в пользу явного левого соединения (LEFT JOIN). Рассмотрим, как должен выглядеть переработанный запрос:


ВЫБРАТЬ
    Пользователи.Ответственный КАК Ответственный,
    Напарники.Ссылка КАК ОтветственныйНапарник
ИЗ
    Справочник.Пользователи КАК Пользователи
    ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Пользователи КАК Напарники
    ПО Пользователи.Ответственный.Напарник = Напарники.Ссылка
ГДЕ
    Пользователи.Наименование ПОДОБНО &Производство
    И НЕ Пользователи.Наименование ПОДОБНО &Ростов
    И (Напарники.Код <> 11 ИЛИ Напарники.Ссылка ЕСТЬ NULL)

Разберем преимущества этого подхода. Используя ЛЕВОЕ СОЕДИНЕНИЕ, мы гарантируем, что основной пользователь останется в выборке, даже если запись в правой таблице (напарники) не будет найдена. В условии ГДЕ мы явно указываем, что нам подходят либо напарники с кодом, не равным 11, либо те записи, где напарника вообще нет (Напарники.Ссылка ЕСТЬ NULL).

Анализ ошибок в исходном коде

Посмотрим на исходный код из сообщения. В нем допущено сразу несколько критических ошибок, которые делают код трудноподдерживаемым и нестабильным:

  1. Магические числа и строки: Проверка Код <> 2 или Родитель.Код <> 3930 крайне ненадежна. При перенумерации справочника или переносе данных в другую базу код элемента может измениться, и логика сломается. Решение: всегда передавайте ссылки на конкретные элементы через параметры запроса или используйте предопределенные элементы.
  2. Проверка на 0 для наименования: Условие Пользователи.Ответственный.Наименование <> 0 технически некорректно, так как наименование — это строка. Сравнение строки с числом в запросе может привести к непредсказуемым результатам или ошибкам на разных СУБД.
  3. Ручное добавление строк в цикле: В коде обхода результата запроса дважды вызывается Добавить(). Это может привести к дублированию строк в табличной части, если логика проверки на пустую ссылку (которая закомментирована) не будет реализована корректно.

Рекомендуемый вариант реализации

Выясним, как написать этот код правильно. Оптимально будет сначала отобрать данные качественным запросом, а затем обработать их в цикле. Обратите внимание на использование параметров и проверку на пустую ссылку:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |    Пользователи.Ответственный КАК Сотрудник,
    |    Пользователи.Ответственный.Напарник КАК Напарник
    |ИЗ
    |    Справочник.Пользователи КАК Пользователи
    |ГДЕ
    |    Пользователи.Наименование ПОДОБНО &Производство
    |    И НЕ Пользователи.Наименование ПОДОБНО &Ростов
    |    И Пользователи.Ответственный <> ЗНАЧЕНИЕ(Справочник.Сотрудники.ПустаяСсылка)
    |    И ЕСТЬNULL(Пользователи.Ответственный.Напарник.Код, 0) <> 11";

Запрос.УстановитьПараметр("Производство", "%Производство%");
Запрос.УстановитьПараметр("Ростов", "%Ростов%");

Выборка = Запрос.Выполнить().Выбрать();

Пока Выборка.Следующий() Цикл
    // Добавляем основного сотрудника
    НоваяСтрока = ТабличнаяЧасть1.Добавить();
    НоваяСтрока.Сотрудник = Выборка.Сотрудник;
    НоваяСтрока.Статус = Перечисления.СтатусВР.Вешал;
    
    // Добавляем напарника, если он заполнен
    Если ЗначениеЗаполнено(Выборка.Напарник) Тогда
        НоваяСтрокаНапарник = ТабличнаяЧасть1.Добавить();
        НоваяСтрокаНапарник.Сотрудник = Выборка.Напарник;
        НоваяСтрокаНапарник.Статус = Перечисления.СтатусВР.Вешал;
    КонецЕсли;
КонецЦикла;

Подведем итоги

Для того чтобы избежать потери данных при работе с пустыми ссылками в запросах, придерживайтесь следующих правил:

← На главную