Как вернуть булево значение из сравнения в запросе 1С и настроить эффективный отбор по году в СКД

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

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

  1. Получение логического (булева) значения непосредственно из выражения сравнения в списке выбора запроса.
  2. Оптимальная фильтрация данных по году в Системе компоновки данных (СКД), когда пользователь вводит только числовое значение года, а отчет должен работать максимально быстро.

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

Часть 1. Как получить булево значение из сравнения прямо в запросе

Представим стандартную задачу: в базе данных есть документ с реквизитом Состояние, который ссылается на перечисление. Нам необходимо в запросе получить поле типа Булево (ИСТИНА или ЛОЖЬ), которое будет принимать значение ИСТИНА, если состояние соответствует значению "Оплачено".

Рассмотрим эволюцию решения этой задачи в платформе 1С:Предприятие.

Классический способ (для старых релизов платформы)

В старых версиях платформы (до релиза 8.3.6) или при включенном режиме совместимости с версиями 8.3.5 и ниже мы не могли написать условие сравнения напрямую в списке выбора полей. Единственным рабочим вариантом было использование условной конструкции ВЫБОР КОГДА ... ТОГДА ... ИНАЧЕ ... КОНЕЦ. Проанализируем пример такого запроса:


ВЫБРАТЬ
    Заказы.Ссылка КАК ДокументСсылка,
    ВЫБОР
        КОГДА Заказы.Состояние = ЗНАЧЕНИЕ(Перечисление.СтатусОплаты.Оплачено) ТОГДА ИСТИНА
        ИНАЧЕ ЛОЖЬ
    КОНЕЦ КАК СтатусЕстьОплата
ИЗ
    Документ.ЗаказПокупателя КАК Заказы

Этот код абсолютно универсален и будет работать на любой версии платформы 1С. Однако он достаточно громоздкий для решения такой простой задачи.

Современный способ (для платформы 8.3.6 и выше)

Начиная с версии платформы 8.3.6, разработчики получили возможность использовать полноценные логические выражения (сравнения с операторами =, <>, >, <, а также логические связки И, ИЛИ, НЕ) непосредственно в списке выбора полей запроса. Теперь мы можем написать код изящно и лаконично, убрав конструкцию выбора:


ВЫБРАТЬ
    Заказы.Ссылка КАК ДокументСсылка,
    Заказы.Состояние = ЗНАЧЕНИЕ(Перечисление.СтатусОплаты.Оплачено) КАК СтатусЕстьОплата
ИЗ
    Документ.ЗаказПокупателя КАК Заказы

Этот запрос вернет поле СтатусЕстьОплата с типом Булево. Обратим внимание, что в тексте запроса крайне важно использовать правильное обращение к предопределенным значениям перечислений через конструкцию ЗНАЧЕНИЕ(...) в единственном числе (именно Перечисление, а не "Перечисления").

Важный нюанс: Работа с реквизитами формы

Часто начинающие разработчики путают контекст выполнения кода и пытаются обратиться к реквизиту формы непосредственно внутри текста запроса. Запрос всегда выполняется на уровне СУБД (базы данных) и ничего «не знает» о существовании экранных форм, их элементов и реквизитов на клиенте.

Если на форме документа у вас есть реквизит Состояние, то для его использования в запросе необходимо обязательно передать его значение через параметр. Посмотрим на примере, как правильно организовать этот процесс:


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

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


&НаКлиенте
Процедура ПроверитьСтатусНаКлиенте()
    
    // Получаем значение перечисления на клиенте без обращения к серверу
    СтатусОплачено = ПредопределенноеЗначение("Перечисление.СтатусОплаты.Оплачено");
    
    Если Объект.Состояние = СтатусОплачено Тогда
        ПоказатьПредупреждение(, "Документ уже оплачен!");КонецЕсли;
    
КонецПроцедуры

Часть 2. Преобразование числового года в тип Дата в запросах

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

Ограничение встроенной функции ДАТАВРЕМЯ

В языке запросов 1С существует функция ДАТАВРЕМЯ(Год, Месяц, День, ...). Однако у нее есть жесткое ограничение: она принимает в качестве аргументов только константы (литералы). Вы не можете передать в нее переменную, параметр или имя поля из таблицы базы данных. Конструкция вида ДАТАВРЕМЯ(Таблица.Год, 1, 1) вызовет синтаксическую ошибку при компиляции запроса.

Классический обходной путь через ДОБАВИТЬКДАТЕ

Чтобы динамически собрать дату на уровне базы данных из числового поля, программисты используют проверенный математический прием с функцией ДОБАВИТЬКДАТЕ. Мы берем за основу минимально возможную базовую дату в системе — 1 января 1 года нашей эры — и прибавляем к ней нужное количество лет, уменьшенное на единицу. Посмотрим, как это выглядит на практике:


ВЫБРАТЬ
    2018 КАК ИсходныйГод
ПОМЕСТИТЬ ВременнаяТаблица
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    // Прибавляем к 0001-01-01 нужное количество лет (например, 2018 - 1 = 2017 лет)
    ДОБАВИТЬКДАТЕ(ДАТАВРЕМЯ(1, 1, 1, 0, 0, 0), ГОД, ВременнаяТаблица.ИсходныйГод - 1) КАК ПолученнаяДата
ИЗ
    ВременнаяТаблица КАК ВременнаяТаблица

В результате СУБД корректно вернет дату 01.01.2018 00:00:00. Этот метод работает быстро и поддерживается всеми СУБД, с которыми работает платформа 1С.

Часть 3. Настройка оптимального отбора по году в СКД

Теперь детально рассмотрим задачу фильтрации документов за определенный год в Системе компоновки данных (СКД). Пользователь хочет вводить на форме отчета только число (например, "2018"), а отчет должен выводить только те документы, которые были созданы в этом году.

Существует два принципиально разных подхода к решению этой задачи: медленный (неправильный) и быстрый (правильный). Проанализируем их влияние на производительность системы.

Неправильный подход: Использование функции ГОД() в секции ГДЕ

Начинающие разработчики часто пишут условие отбора следующим образом:


ГДЕ
    ГОД(ЗаявкиНаРасходованиеСредств.Дата) = &ВыбранныйГод

Почему так делать категорически нельзя на реальных базах данных?

Поле Дата во всех стандартных таблицах документов 1С является индексируемым полем. Индексы позволяют СУБД мгновенно находить нужные строки без сканирования всей таблицы. Однако, когда мы применяем любую функцию (в данном случае ГОД()) к индексируемому полю в условии фильтрации, СУБД теряет возможность использовать индекс (запрос становится non-SARGable).

Вместо быстрого поиска по индексу (Index Seek) СУБД будет вынуждена произвести полное сканирование таблицы документов (Table Scan / Index Scan), вычисляя функцию года для каждой строки базы данных. На базе с миллионами документов такой отчет просто «повесит» систему.

Правильный подход: Фильтрация по периоду МЕЖДУ Начало и Конец даты

Самым производительным решением является фильтрация по диапазону дат с использованием оператора МЕЖДУ. В этом случае СУБД мгновенно находит записи по индексу:


ГДЕ
    ЗаявкиНаРасходованиеСредств.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания

Однако мы не хотим заставлять пользователя вручную вводить две даты (01.01.2018 и 31.12.2018), когда ему нужен просто год. Мы можем автоматизировать расчет граничных дат прямо в схеме компоновки данных (СКД).

Пошаговая инструкция настройки автоматического расчета дат в СКД:

  1. Откроем Схему компоновки данных нашего отчета и перейдем на вкладку Параметры.
  2. Добавим новый параметр с именем &ВыбранныйГод. Установим для него тип данных Число (длина 4, без дробной части). Этот параметр будет доступен пользователю для ввода на форме.
  3. Для системных параметров &ДатаНачала и &ДатаОкончания установим флаг Ограничение доступности. Это скроет их из интерфейса пользователя, чтобы они не загромождали форму.
  4. В поле Выражение для параметра &ДатаНачала пропишем формулу вычисления начала года на основе введенного числа:

    ДобавитьКДате(ДатаВремя(1, 1, 1), "Год", &ВыбранныйГод - 1)

  5. В поле Выражение для параметра &ДатаОкончания пропишем формулу вычисления конца года:

    КонецПериода(ДобавитьКДате(ДатаВремя(1, 1, 1), "Год", &ВыбранныйГод - 1), "Год")

Благодаря такой настройке, когда пользователь введет в отчете число "2018", СКД автоматически рассчитает граничные параметры:

В СУБД уйдет чистый и оптимизированный SQL-запрос с поиском по диапазону дат, который отработает за доли секунды.

Альтернатива: СтандартныйПериод в СКД

Если специфика отчета позволяет, вместо ввода числа года можно использовать стандартный системный тип данных 1С — СтандартныйПериод. В интерфейсе программы он выглядит как удобный выпадающий список, в котором пользователь может выбрать готовые интервалы: "Этот год", "Прошлый год", "Январь", "2018 год" и т. д.

Для связывания стандартного периода с границами запроса в параметрах СКД достаточно настроить следующие выражения:

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

← На главную