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

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

В процессе разработки на платформе «1С:Предприятие 8» программисты часто сталкиваются с ситуацией, когда визуально правильный запрос выдает неожиданные результаты или игнорирует установленные фильтры. Особенно это характерно для конструкций, использующих сравнение нескольких полей (кортежей) и работу с типом Дата. Разберем подобную ситуацию на конкретном примере, проанализируем причины возникновения ошибки и найдем наиболее эффективные способы её решения.

Анализ исходной проблемы

Рассмотрим ситуацию: у нас есть запрос, который выбирает данные из табличной части документа и должен исключать те записи, которые уже присутствуют в регистре сведений. Для этого используется конструкция НЕ (Поле1, Поле2) В (ВЫБРАТЬ...). Однако, несмотря на наличие соответствующих записей в регистре, отбор не срабатывает, и запрос возвращает все строки.

Исходный код запроса выглядит следующим образом:


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

Причина №1: Коварство типа Дата и состава даты в метаданных

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

Рассмотрим два варианта:

  1. Дата и время: Хранит полное значение (например, 01.10.2023 14:30:05).
  2. Дата: В пользовательском интерфейсе отображается только дата, а в базе данных время всегда записывается как 00:00:00.

Проанализируем ситуацию автора: в табличной части документа ДатаЗвонка содержала время (например, момент реального звонка), а в регистре сведений для аналогичного реквизита был установлен состав «Дата». При сравнении система видела, что 01.10.2023 14:30:05 не равно 01.10.2023 00:00:00. Логически это разные моменты времени, поэтому условие В не находило совпадений, и фильтрация не работала.

Решение: Приведение реквизитов к единому составу даты в конфигураторе (установка значения «Дата и время» для обоих полей) или использование программных методов нормализации в запросе.

Причина №2: Отсутствие нормализации дат в запросе

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


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

Функция НАЧАЛОПЕРИОДА(..., ДЕНЬ) отсекает время, приводя его к началу суток. Это «золотой стандарт» при сравнении дат, если точность до секунд не критична для бизнес-логики.

Оптимизация: Переход от конструкции «НЕ В» к «ЛЕВОЕ СОЕДИНЕНИЕ»

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

Разберем этот метод по шагам:

  1. Мы берем основную таблицу (звонки из документа).
  2. Присоединяем к ней таблицу регистра сведений по двум полям (Заказ и Дата).
  3. В условии ГДЕ указываем, что запись из регистра не найдена. Если записи нет, поля присоединенной таблицы будут содержать NULL.

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


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

Преимущества этого метода:

Важность индексирования

Чтобы наш запрос работал быстро, необходимо убедиться, что поля, по которым идет сравнение, проиндексированы. В регистре сведений измерения индексируются автоматически. Если же ДатаЗвонка — это реквизит регистра, обязательно нужно зайти в конфигуратор и установить у него свойство Индексировать.

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

Использование временных таблиц для чистоты кода

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

Пример структуры такого запроса:


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

// Шаг 2: Основная выборка с исключением
ВЫБРАТЬ
    ЗаказСервисЗвонки.Ссылка,
    ЗаказСервисЗвонки.ДатаЗвонка
ИЗ
    Документ.ЗаказСервис.Звонки КАК ЗаказСервисЗвонки
    ЛЕВОЕ СОЕДИНЕНИЕ ВТ_УжеОтправленные КАК ВТ
        ПО ЗаказСервисЗвонки.Ссылка = ВТ.Заказ
        И (НАЧАЛОПЕРИОДА(ЗаказСервисЗвонки.ДатаЗвонка, ДЕНЬ) = ВТ.ДатаДень)
ГДЕ
    ВТ.Заказ ЕСТЬ NULL

Подведем итоги

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

Основные выводы нашего разбора:

Соблюдение этих правил позволит вам писать надежные и быстрые запросы, избегая трудноуловимых ошибок при работе с датами в 1С.

← На главную