Почему результат запроса 1С отображается как "Ложь", хотя в консоли он работает правильно, и что делать, если запрос "виснет"?

Программист 1С v8.3 (Управляемые формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← К списку

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

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

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

1. Почему метод Запрос.Выполнить() не может возвращать Ложь?

Начнем с самого главного заблуждения. В системе 1С:Предприятие метод Запрос.Выполнить() ведет себя предсказуемо и строго определенно:

  1. Он всегда возвращает объект типа РезультатЗапроса. Этот объект содержит данные, полученные в результате выполнения запроса, или является пустым, если запрос не вернул ни одной строки.
  2. В случае возникновения ошибки при выполнении запроса (например, синтаксическая ошибка, ошибка подключения к базе данных), метод Запрос.Выполнить() выбросит исключение. Он никогда не вернет булево значение Ложь.

В чем же тогда причина, если в отладчике мы видим "Ложь"?

Если вы видите в табло отладчика или при оценке выражения, что РезультатЗапроса = Запрос.Выполнить() дает значение Ложь, это не означает, что сам метод Запрос.Выполнить() вернул булево значение. Давайте рассмотрим подробнее, что происходит в такой ситуации:

  1. Оценка выражения в отладчике: Отладчик оценивает не только результат выполнения метода, но и все выражение целиком. Если до присвоения переменная РезультатЗапроса имела значение Неопределено или была каким-либо образом приведена к булевому Ложь, то при оценке выражения РезультатЗапроса = Запрос.Выполнить() отладчик может показать Ложь как результат *сравнения* или *присвоения* в определенном контексте, а не как возвращаемое значение метода. По сути, он говорит вам, что "значение переменной РезультатЗапроса (которое может быть Неопределено) не равно значению, возвращенному методом Запрос.Выполнить()", если переменная не была инициализирована.
  2. Фактический результат: Независимо от того, что отображает отладчик в выражении присвоения, переменная, которой вы присваиваете результат Запрос.Выполнить(), в итоге будет содержать объект РезультатЗапроса.

Как правильно проверять, вернул ли запрос данные?

Для проверки того, содержит ли РезультатЗапроса какие-либо строки, используйте метод Пустой() объекта РезультатЗапроса. Этот метод возвращает булево значение Истина, если запрос не вернул ни одной строки, и Ложь, если данные есть.

Давайте посмотрим на пример правильного использования:


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

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

2. Оптимизация запросов: Когда запрос работает медленно или "виснет"?

Теперь, когда мы разобрались с тем, что Запрос.Выполнить() всегда возвращает РезультатЗапроса, давайте перейдем к другой распространенной проблеме, упомянутой в исходной теме: медленная работа или "зависание" запросов. Часто это связано с неоптимальным использованием виртуальных таблиц и общими принципами построения запросов.

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

2.1. Передача условий в параметры виртуальных таблиц

Это, пожалуй, самый важный и эффективный способ оптимизации запросов, работающих с виртуальными таблицами регистров (например, Остатки, ОстаткиИОбороты, СрезПоследних, СрезПервых). Многие разработчики по ошибке извлекают все данные из виртуальной таблицы, а затем фильтруют их в секции ГДЕ основного запроса.

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

Решение: Всегда передавайте условия отбора непосредственно в параметры виртуальной таблицы. Это позволяет СУБД отфильтровать данные на самом раннем этапе, существенно сокращая объем обрабатываемой информации.

Рассмотрим пример:


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

В этом примере платформа сначала сформирует остатки по всем складам и всей номенклатуре, а затем уже будет фильтровать их по складу и виду номенклатуры. Это может быть очень медленно.

Теперь давайте посмотрим, как правильно:


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

Здесь СУБД сразу формирует остатки только для нужного склада и вида номенклатуры, что значительно ускоряет выполнение запроса. Всегда стремитесь переносить условия в параметры виртуальных таблиц!

2.2. Ограничения и рекомендации по параметрам виртуальных таблиц

Хотя передача условий в параметры виртуальных таблиц очень эффективна, есть свои нюансы:

  1. Простые условия: Старайтесь использовать в параметрах виртуальных таблиц максимально простые условия, такие как "Измерение = Значение", "Измерение В (&СписокЗначений)".
  2. Избегайте сложных выражений: Не рекомендуется использовать подзапросы, соединения или сложные функции в параметрах виртуальных таблиц, так как это может сбить оптимизатор СУБД и привести к обратному эффекту.

2.3. Работа с составными типами данных

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

Решение: Используйте функцию ВЫРАЗИТЬ() для явного приведения поля к нужному типу. Это поможет СУБД сразу выбрать нужную таблицу.


// Плохой пример: поле составного типа без ВЫРАЗИТЬ
Запрос.Текст = "ВЫБРАТЬ
               |    Документ.ПоступлениеТоваровУслуг.Контрагент.Наименование
               |ИЗ
               |    Документ.ПоступлениеТоваровУслуг";

// Хороший пример: с использованием ВЫРАЗИТЬ
Запрос.Текст = "ВЫБРАТЬ
               |    ВЫРАЗИТЬ(Документ.ПоступлениеТоваровУслуг.Контрагент КАК Справочник.Контрагенты).Наименование
               |ИЗ
               |    Документ.ПоступлениеТоваровУслуг";

Второй вариант будет работать быстрее, так как платформа точно знает, из какой таблицы брать наименование.

2.4. Использование временных таблиц

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

Преимущества:

  1. СУБД может лучше оптимизировать каждый отдельный шаг.
  2. К временным таблицам могут быть применены индексы, что ускоряет последующие соединения.
  3. Уменьшается объем данных, передаваемых между этапами запроса.

Рассмотрим общий принцип:


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

2.5. Избегайте логического "ИЛИ" в условиях

По возможности старайтесь избегать использования логической связки "ИЛИ" в секции ГДЕ, особенно если условия по разным полям. "ИЛИ" часто мешает СУБД эффективно использовать индексы, что приводит к полному сканированию таблиц.

Если есть возможность, перепишите запрос с использованием ОБЪЕДИНИТЬ ВСЕ или используйте конструкцию В (&СписокЗначений).

2.6. Явное упорядочивание результатов

Если для вашего алгоритма обработки важен порядок записей, всегда используйте предложение УПОРЯДОЧИТЬ ПО. Без него порядок записей не гарантируется и может меняться в зависимости от версии платформы, СУБД или других факторов. Отсутствие УПОРЯДОЧИТЬ ПО при необходимости может привести к логическим ошибкам в коде.

2.7. Инструменты отладки и анализа запросов

Для эффективной отладки и оптимизации запросов активно используйте следующие инструменты:

  1. Консоль запросов: Позволяет выполнять запросы, просматривать их результаты, а также анализировать план выполнения запроса (если ваша СУБД это поддерживает). Это лучший способ проверить, что запрос возвращает именно те данные, которые вы ожидаете.
  2. Журнал регистрации: В нем могут фиксироваться медленные запросы, что поможет вам выявить проблемные участки кода.
  3. Технологический журнал: Более продвинутый инструмент для анализа производительности, позволяющий получать детальную информацию о каждом шаге выполнения запроса, времени его выполнения и использовании ресурсов.

Мы рассмотрели две основные ситуации, связанные с запросами в 1С:Предприятие. Помните, что понимание базовых принципов работы методов платформы и правил оптимизации запросов — это ключ к созданию производительных и надежных решений. Активно используйте предложенные методы, и ваши запросы перестанут быть источником головной боли!

← К списку