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

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

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

Рассмотрим типовую ситуацию: у нас есть поле ДатаОтгрузки в таблице, которое хранит дату и время (например, 2023-01-15 10:30:00). Если мы хотим найти все записи за 15 января 2023 года, используя один параметр &ДатаОтгрузки, передавая в него, например, дату 2023-01-15 00:00:00, простое условие равенства ДатаОтгрузки = &ДатаОтгрузки не сработает корректно. Оно отберет только те записи, у которых дата и время совпадут вплоть до секунды, что не соответствует нашей задаче отбора за весь день.

Для решения этой проблемы мы изучим два основных подхода, один из которых является предпочтительным с точки зрения производительности.

Особенности работы с датами в 1С

Прежде чем углубиться в решения, давайте проясним, как 1С работает с датами. Важно понимать, что в системе 1С поле типа "Дата" всегда содержит компонент времени. Даже если вы явно не указываете время при создании даты, оно автоматически устанавливается в 00:00:00. Таким образом, дата 2023-01-15 на самом деле хранится как 2023-01-15 00:00:00. Это фундаментальное понимание помогает избежать распространенных ошибок при фильтрации.

Решение 1: Рекомендуемый подход — использование диапазона с функциями НАЧАЛОПЕРИОДА() и КОНЕЦПЕРИОДА() на параметре

Наиболее эффективным и рекомендованным способом фильтрации по дате без учета времени с использованием одного параметра является задание диапазона "от" и "до" для этого дня. Этот подход основан на применении функций НАЧАЛОПЕРИОДА() и КОНЕЦПЕРИОДА() к параметру запроса, а не к полю таблицы.

  1. Определяем диапазон дня: Мы хотим найти все записи, у которых дата отгрузки находится в промежутке с 00:00:00 начала выбранного дня до 23:59:59 конца того же дня.
  2. Применяем функции к параметру: Мы используем наш единственный параметр &ДатаОтгрузки, чтобы определить границы этого диапазона. Функция НАЧАЛОПЕРИОДА(&ДатаОтгрузки, ДЕНЬ) вернет начало дня (например, 2023-01-15 00:00:00 для &ДатаОтгрузки = 2023-01-15 10:00:00), а КОНЕЦПЕРИОДА(&ДатаОтгрузки, ДЕНЬ) вернет конец дня (например, 2023-01-15 23:59:59).
  3. Формируем условие: Условие в запросе будет выглядеть следующим образом:

ГДЕ ВТ_ДанныеИзЗаказа.ЗаказСсылка.ДатаОтгрузки >= НАЧАЛОПЕРИОДА(&ДатаОтгрузки, ДЕНЬ)
  И ВТ_ДанныеИзЗаказа.ЗаказСсылка.ДатаОтгрузки <= КОНЕЦПЕРИОДА(&ДатаОтгрузки, ДЕНЬ)

Рассмотрим этот пример подробнее. Представьте, что в параметр &ДатаОтгрузки мы передали значение 2023-01-15 12:00:00. Тогда:

  1. НАЧАЛОПЕРИОДА(&ДатаОтгрузки, ДЕНЬ) превратится в 2023-01-15 00:00:00.
  2. КОНЕЦПЕРИОДА(&ДатаОтгрузки, ДЕНЬ) превратится в 2023-01-15 23:59:59.

Таким образом, наше условие фактически преобразуется в:


ГДЕ ВТ_ДанныеИзЗаказа.ЗаказСсылка.ДатаОтгрузки >= '2023-01-15 00:00:00'
  И ВТ_ДанныеИзЗаказа.ЗаказСсылка.ДатаОтгрузки <= '2023-01-15 23:59:59'

Почему этот подход является оптимальным?

Преимущество этого подхода заключается в том, что функции НАЧАЛОПЕРИОДА() и КОНЕЦПЕРИОДА() применяются к параметру запроса (&ДатаОтгрузки), а не к самому полю таблицы (ВТ_ДанныеИзЗаказа.ЗаказСсылка.ДатаОтгрузки). Это критически важно для производительности. Когда функции применяются к полю таблицы в условии (например, НАЧАЛОПЕРИОДА(Поле, ДЕНЬ)), СУБД вынуждена вычислить значение функции для каждой записи в таблице перед сравнением. Это действие эффективно "скрывает" исходное значение поля от индексов. В результате, если по полю ДатаОтгрузки существует индекс, СУБД не сможет его использовать и будет вынуждена произвести полное сканирование таблицы, что значительно замедлит выполнение запроса, особенно на больших объемах данных.

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

Функции НАЧАЛОПЕРИОДА() и КОНЕЦПЕРИОДА()

Эти функции языка запросов 1С являются незаменимыми помощниками при работе с датами. Они принимают два параметра:

  1. Дата: Исходная дата, для которой мы хотим определить начало или конец периода.
  2. ТипПериода: Единица измерения периода. Может принимать следующие значения:
    • СЕКУНДА
    • МИНУТА
    • ЧАС
    • ДЕНЬ (используется в нашем случае)
    • НЕДЕЛЯ
    • МЕСЯЦ
    • КВАРТАЛ
    • ПОЛУГОДИЕ
    • ГОД

Используя ДЕНЬ в качестве ТипПериода, мы гарантируем, что получим точные границы для выбранного дня.

Решение 2: Менее эффективный подход — использование НАЧАЛОПЕРИОДА() на поле таблицы

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


ГДЕ ВТ_ДанныеИзЗаказа.ЗаказСсылка.Партнер = &Партнер
  И НАЧАЛОПЕРИОДА(ВТ_ДанныеИзЗаказа.ЗаказСсылка.ДатаОтгрузки, ДЕНЬ) = НАЧАЛОПЕРИОДА(&ДатаОтгрузки, ДЕНЬ)

Или, если параметр &ДатаОтгрузки уже подготовлен как начало дня:


ГДЕ ВТ_ДанныеИзЗаказа.ЗаказСсылка.Партнер = &Партнер
  И НАЧАЛОПЕРИОДА(ВТ_ДанныеИзЗаказа.ЗаказСсылка.ДатаОтгрузки, ДЕНЬ) = &ДатаОтгрузки

Хотя этот подход функционально верен и дает правильный результат (сравнивая только дневную часть дат), его главный недостаток заключается в том, что применение функции к полю таблицы (ВТ_ДанныеИзЗаказа.ЗаказСсылка.ДатаОтгрузки) препятствует использованию индексов. Как мы уже выяснили, СУБД будет вынуждена перебрать каждую запись в таблице, вычислить для нее НАЧАЛОПЕРИОДА и только потом сравнить. Это приводит к значительному замедлению запроса на больших таблицах, поэтому данный метод не рекомендуется для использования в высоконагруженных системах или при работе с большим объемом данных.

Пример полного запроса с оптимальным решением

Возьмем исходный запрос и применим к нему оптимальное условие фильтрации по дате:


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

В этом запросе мы используем один параметр &ДатаОтгрузки, который может быть любой датой в пределах интересующего нас дня. Система 1С, благодаря функциям НАЧАЛОПЕРИОДА и КОНЕЦПЕРИОДА, корректно определит границы дня и выполнит отбор с максимальной производительностью.

Подготовка параметра &ДатаОтгрузки

Когда вы передаете параметр &ДатаОтгрузки в запрос из кода 1С, вы можете передать любую дату, которая относится к интересующему дню. Например, если вы хотите получить данные за 15 января 2023 года, можно установить параметр следующим образом:


ДатаДляОтбора = Дата(2023, 1, 15); // Это будет 2023-01-15 00:00:00
// Или:
// ДатаДляОтбора = ТекущаяДата(); // Это будет текущая дата и время
// Или:
// ДатаДляОтбора = Новый Дата(2023, 1, 15, 10, 30, 0); // Любая дата в течение дня

Запрос = Новый Запрос;
Запрос.Текст = "
    |ВЫБРАТЬ ...
    |ГДЕ ... И ВТ_ДанныеИзЗаказа.ЗаказСсылка.ДатаОтгрузки >= НАЧАЛОПЕРИОДА(&ДатаОтгрузки, ДЕНЬ)
    |  И ВТ_ДанныеИзЗаказа.ЗаказСсылка.ДатаОтгрузки <= КОНЕЦПЕРИОДА(&ДатаОтгрузки, ДЕНЬ)";

Запрос.УстановитьПараметр("ДатаОтгрузки", ДатаДляОтбора);

Вне зависимости от того, какое время будет в переменной ДатаДляОтбора (00:00:00, 10:30:00 или 23:59:59), функции НАЧАЛОПЕРИОДА(&ДатаОтгрузки, ДЕНЬ) и КОНЕЦПЕРИОДА(&ДатаОтгрузки, ДЕНЬ) всегда корректно вычислят начало и конец дня, соответствующего переданной дате.

Выводы

При работе с датами в запросах 1С, где требуется отбор по конкретному дню без учета времени, всегда отдавайте предпочтение методу формирования диапазона с помощью функций НАЧАЛОПЕРИОДА() и КОНЕЦПЕРИОДА(), применяемых к параметру запроса. Этот подход обеспечивает не только корректный результат, но и максимально возможную производительность за счет эффективного использования индексов базы данных. Избегайте применения функций обработки даты непосредственно к полям таблицы в условиях ГДЕ, чтобы не нивелировать преимущества индексов.

Рассмотренный метод является стандартом де-факто для эффективной работы с датами в запросах 1С и позволяет создавать быстрые и надежные механизмы отбора данных в ваших решениях. Для решения сопутствующих задач при работе с периодами пригодится разбор способов построения выборок по диапазонам дат.

← На главную