В практике разработки на платформе 1С:Предприятие одной из самых распространенных «ловушек» для начинающих программистов является использование обращений к реквизитам ссылочных полей через точку (разыменование ссылок) непосредственно в секции ГДЕ (WHERE) текста запроса. Это приводит к неожиданному «исчезновению» строк из итоговой выборки, если ссылка пустая. Рассмотрим подробно, почему так происходит и как правильно составить запрос, чтобы данные не терялись.
Проанализируем ситуацию, представленную в вопросе автора. В тексте запроса используются условия вида Пользователи.Ответственный.Напарник.Код <> 11. На первый взгляд кажется, что система просто должна проверить код напарника. Однако на уровне СУБД (SQL) платформа 1С преобразует это обращение через точку в внутреннее соединение (INNER JOIN).
Разберем по шагам, что происходит внутри системы:
Пользователи.Ответственный, система соединяет таблицу пользователей с таблицей сотрудников (или кто является типом данного поля).Ответственный.Напарник, система выполняет еще одно соединение с таблицей пользователей.ГДЕ стоит условие на конечное поле этой цепочки, платформа использует INNER JOIN для всей цепочки соединений.Важный момент: Если у основного пользователя поле Напарник не заполнено (содержит пустую ссылку), то внутреннее соединение с таблицей напарников не найдет соответствующей записи. В результате вся строка основного пользователя будет исключена из результата запроса, даже если сам пользователь полностью соответствует остальным критериям фильтрации. Это и есть причина того, почему «пользователь без напарника исчезает».
Самый быстрый способ «вылечить» такой запрос без радикальной переработки его структуры — использование функции ЕСТЬNULL. Эта функция позволяет подставить альтернативное значение в случае, если результат обращения через точку возвращает NULL (что и происходит, когда ссылка пуста).
Проанализируем, как изменится условие. Вместо прямого сравнения мы обернем обращение к коду:
|ГДЕ
| ЕСТЬNULL(Пользователи.Ответственный.Напарник.Код, 0) <> 11
В данном случае, если у ответственного нет напарника, функция ЕСТЬNULL вернет 0. Поскольку 0 <> 11 — это ИСТИНА, строка с пользователем попадет в результат. Однако стоит помнить, что такой подход может негативно сказаться на производительности (индексы по полю Код могут не использоваться), поэтому для больших таблиц лучше применять другие методы.
Наиболее правильным и «прозрачным» с точки зрения SQL методом является отказ от обращений через точку в пользу явного левого соединения (LEFT JOIN). Рассмотрим, как должен выглядеть переработанный запрос:
ВЫБРАТЬ
Пользователи.Ответственный КАК Ответственный,
Напарники.Ссылка КАК ОтветственныйНапарник
ИЗ
Справочник.Пользователи КАК Пользователи
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Пользователи КАК Напарники
ПО Пользователи.Ответственный.Напарник = Напарники.Ссылка
ГДЕ
Пользователи.Наименование ПОДОБНО &Производство
И НЕ Пользователи.Наименование ПОДОБНО &Ростов
И (Напарники.Код <> 11 ИЛИ Напарники.Ссылка ЕСТЬ NULL)
Разберем преимущества этого подхода. Используя ЛЕВОЕ СОЕДИНЕНИЕ, мы гарантируем, что основной пользователь останется в выборке, даже если запись в правой таблице (напарники) не будет найдена. В условии ГДЕ мы явно указываем, что нам подходят либо напарники с кодом, не равным 11, либо те записи, где напарника вообще нет (Напарники.Ссылка ЕСТЬ NULL).
Посмотрим на исходный код из сообщения. В нем допущено сразу несколько критических ошибок, которые делают код трудноподдерживаемым и нестабильным:
Код <> 2 или Родитель.Код <> 3930 крайне ненадежна. При перенумерации справочника или переносе данных в другую базу код элемента может измениться, и логика сломается. Решение: всегда передавайте ссылки на конкретные элементы через параметры запроса или используйте предопределенные элементы.Пользователи.Ответственный.Наименование <> 0 технически некорректно, так как наименование — это строка. Сравнение строки с числом в запросе может привести к непредсказуемым результатам или ошибкам на разных СУБД.Добавить(). Это может привести к дублированию строк в табличной части, если логика проверки на пустую ссылку (которая закомментирована) не будет реализована корректно.Выясним, как написать этот код правильно. Оптимально будет сначала отобрать данные качественным запросом, а затем обработать их в цикле. Обратите внимание на использование параметров и проверку на пустую ссылку:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Пользователи.Ответственный КАК Сотрудник,
| Пользователи.Ответственный.Напарник КАК Напарник
|ИЗ
| Справочник.Пользователи КАК Пользователи
|ГДЕ
| Пользователи.Наименование ПОДОБНО &Производство
| И НЕ Пользователи.Наименование ПОДОБНО &Ростов
| И Пользователи.Ответственный <> ЗНАЧЕНИЕ(Справочник.Сотрудники.ПустаяСсылка)
| И ЕСТЬNULL(Пользователи.Ответственный.Напарник.Код, 0) <> 11";
Запрос.УстановитьПараметр("Производство", "%Производство%");
Запрос.УстановитьПараметр("Ростов", "%Ростов%");
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
// Добавляем основного сотрудника
НоваяСтрока = ТабличнаяЧасть1.Добавить();
НоваяСтрока.Сотрудник = Выборка.Сотрудник;
НоваяСтрока.Статус = Перечисления.СтатусВР.Вешал;
// Добавляем напарника, если он заполнен
Если ЗначениеЗаполнено(Выборка.Напарник) Тогда
НоваяСтрокаНапарник = ТабличнаяЧасть1.Добавить();
НоваяСтрокаНапарник.Сотрудник = Выборка.Напарник;
НоваяСтрокаНапарник.Статус = Перечисления.СтатусВР.Вешал;
КонецЕсли;
КонецЦикла;
Для того чтобы избежать потери данных при работе с пустыми ссылками в запросах, придерживайтесь следующих правил:
ГДЕ — это скрытый INNER JOIN.ЕСТЬNULL() или переходите к явному описанию ЛЕВОЕ СОЕДИНЕНИЕ.Код <> 11). Используйте параметры или дополнительные реквизиты (например, булево поле ИсключитьИзОбработки).