Как в запросе 1С выполнить вторую выборку, если первая не нашла значение

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

В практике разработки на платформе 1С:Предприятие 8.3 часто возникает ситуация, когда необходимо получить данные из основного источника, но если их там нет — обратиться к резервному. Например, мы ищем цену товара в специальном прайс-листе, и если она не задана, берем базовую цену из карточки номенклатуры. В этой статье мы подробно разберем, как реализовать такую логику эффективно и красиво.

Рассмотрим несколько подходов: от простого разделения кода до сложных конструкций внутри одного запроса. Мы проанализируем ситуацию с точки зрения производительности и удобства поддержки кода.

Метод 1: Использование объединения с приоритетом (UNION ALL)

Это самый популярный и зачастую самый «красивый» способ решить задачу в рамках одного запроса. Суть метода заключается в том, чтобы объединить результаты двух запросов и добавить техническое поле, которое будет отвечать за порядок строк. Проанализируем, как это работает на практике.

Предположим, нам нужно найти контактное лицо контрагента. Сначала ищем основного менеджера, а если его нет — любого сотрудника этой компании.

Разберем по шагам создание такого запроса:

  1. В первом подзапросе выбираем данные и добавляем поле Приоритет со значением 1.
  2. Во втором подзапросе выбираем альтернативные данные и добавляем поле Приоритет со значением 2.
  3. Объединяем их через ОБЪЕДИНИТЬ ВСЕ.
  4. Упорядочиваем весь результат по полю Приоритет.
  5. Используем конструкцию ВЫБРАТЬ ПЕРВЫЕ 1.

Посмотрим на пример кода:


ВЫБРАТЬ ПЕРВЫЕ 1
    Т.Ссылка КАК Результат,
    1 КАК Приоритет
ИЗ
    Справочник.КонтактныеЛица КАК Т
ГДЕ
    Т.Владелец = &Контрагент
    И Т.Основной = ИСТИНА

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

ВЫБРАТЬ ПЕРВЫЕ 1
    Т.Ссылка,
    2
ИЗ
    Справочник.КонтактныеЛица КАК Т
ГДЕ
    Т.Владелец = &Контрагент

УПОРЯДОЧИТЬ ПО
    Приоритет

В данном случае, если первый запрос найдет запись, она получит приоритет 1 и станет первой в списке. Если же первый запрос вернет пустой результат, то в итоговой выборке останется только запись из второго запроса с приоритетом 2, которая и попадет в результат благодаря ВЫБРАТЬ ПЕРВЫЕ 1.

Метод 2: Последовательное выполнение в коде 1С

Иногда попытка объединить всё в один запрос может привести к снижению производительности. Разберем ситуацию: первый запрос очень легкий (поиск по индексированному полю), а второй — тяжелый (выборка из огромного регистра с агрегатами). Если мы объединим их, сервер СУБД может начать выполнять оба запроса одновременно для построения плана, что излишне нагрузит систему.

Выясним, как сделать это правильно через встроенный язык:


Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ Ссылка ИЗ Справочник.Номенклатура ГДЕ Артикул = &Артикул";
Запрос.УстановитьПараметр("Артикул", ИскомыйАртикул);

Результат = Запрос.Выполнить();

Если Результат.Пустой() Тогда
    // Если по артикулу не нашли, ищем по наименованию
    Запрос.Текст = "ВЫБРАТЬ Ссылка ИЗ Справочник.Номенклатура ГДЕ Наименование ПОДОБНО &Наименование";
    Запрос.УстановитьПараметр("Наименование", "%" + ИскомоеИмя + "%");
    Результат = Запрос.Выполнить();
КонецЕсли;

Если Не Результат.Пустой() Тогда
    Выборка = Результат.Выбрать();
    Выборка.Следующий();
    // Работаем с результатом
КонецЕсли;

Этот метод считается наиболее надежным в плане производительности, так как второй запрос (потенциально тяжелый) вообще не будет отправлен на сервер СУБД, если первый вернул данные.

Метод 3: Эмуляция EXISTS через пакетные запросы

В языке запросов 1С нет прямого оператора EXISTS, как в стандартном SQL, но мы можем имитировать его поведение. Это полезно, если вы работаете в сложном динамическом списке или в СКД, где нужно все упаковать в один текст запроса.

Рассмотрим алгоритм решения:

  1. Создаем временную таблицу, в которую помещаем результат первого поиска.
  2. Во втором запросе пакета делаем выборку из альтернативного источника.
  3. В секции ГДЕ второго запроса прописываем условие отсутствия записей во временной таблице.

Посмотрим на пример реализации:


// Помещаем результат первой проверки во временную таблицу
ВЫБРАТЬ ПЕРВЫЕ 1
    Т.Ссылка КАК Ссылка
ПОМЕСТИТЬ ВТ_ПервыйПоиск
ИЗ
    Справочник.Склады КАК Т
ГДЕ
    Т.Код = "ОСНОВНОЙ";

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

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

ВЫБРАТЬ
    Т.Ссылка
ИЗ
    ВТ_ПервыйПоиск КАК Т

Проанализируем конструкцию ГДЕ НЕ 1 В (ВЫБРАТЬ ПЕРВЫЕ 1 1 ИЗ ВТ_ПервыйПоиск). Это эффективный способ сказать серверу: "Не делай эту выборку, если во временной таблице уже что-то есть". Большинство современных СУБД (MS SQL Server, PostgreSQL) корректно оптимизируют такой запрос и не будут сканировать таблицы второго запроса, если условие не выполняется.

Метод 4: Левое соединение с проверкой на NULL

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

Разберем пример получения остатков. Допустим, у нас есть "Основной склад" и "Резервный склад". Нам нужно понять, сколько товара брать в работу.


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

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

Выбор оптимального решения

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

1. Сложность запросов

Если оба запроса простые (выборка по ссылке или коду), используйте Метод 1 (UNION ALL). Это позволит выполнить всё за один сеанс обращения к базе данных, что экономит время на установку соединения и передачу текста запроса.

2. Нагрузка на сервер

Если второй запрос ресурсоемкий, лучше использовать Метод 2 (Два запроса в коде). Это гарантирует, что тяжелая часть кода выполнится только тогда, когда она действительно необходима.

3. Использование в СКД или списках

Если вы ограничены одним текстом запроса (например, в настройках схемы компоновки данных), ваш выбор — Метод 3 (Временные таблицы) или Метод 4 (Соединения).

Важные замечания по производительности

При использовании временных таблиц (Метод 3) не забывайте об индексации. Если в первом запросе может быть много строк, которые вы потом используете для соединения или фильтрации, добавление индекса в секции ИНДЕКСИРОВАТЬ ПО значительно ускорит процесс.

Также стоит помнить про механизм RLS (ограничение доступа на уровне записей). Если у пользователя нет прав на одну из таблиц в объединенном запросе, весь запрос может завершиться ошибкой или вернуть пустой результат. В таких случаях разделение на два отдельных вызова Запрос.Выполнить() с проверкой прав через ПравоДоступа() будет более безопасным решением.

Подводя итог, можно сказать, что "красивое" решение не всегда является самым эффективным. Для простых задач идеально подходит ОБЪЕДИНИТЬ ВСЕ с приоритетом, а для сложных систем — логическое разделение на уровне встроенного языка 1С.

← На главную