Строка вместо даты в запросе 1С: как привести тип и не потерять скорость

· 9 мин

Содержание

Язык запросов 1С не умеет превращать строку в дату. В нём нет аналога функции Дата() из встроенного языка, а ДАТАВРЕМЯ(2024, 1, 31) принимает только числа-константы, записанные прямо в тексте. Поэтому дату, пришедшую строкой, приводят к типу Дата до запроса, во встроенном языке, и передают через параметр: Запрос.УстановитьПараметр("НачалоПериода", Дата("20240101")).

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

Откуда в запросе берётся дата в виде строки

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

  • 📥 Загрузка из файла. Excel, CSV, XML или JSON от внешней системы отдают дату текстом: 31.01.2024, 2024-01-31T10:15:00, 20240131.
  • 🧩 Реквизит не того типа. Разработчик когда-то завёл поле «Дата договора» типа Строка(10), и теперь по нему нужно фильтровать.
  • 🧾 Дополнительные реквизиты и сведения. Пользователи иногда создают дополнительный реквизит со строковым типом значения и вписывают туда даты вручную.
  • 🔧 Сборка текста запроса конкатенацией. Дату вклеивают в текст запроса через +, и она превращается в строку при склейке.
  • 🖥️ Параметр с формы. Поле ввода на форме имеет тип Строка, значение без проверки уходит в УстановитьПараметр.

Первые три случая — это данные, с которыми придётся жить. Последние два — ошибки в коде, их исправляют в источнике.

Чем привести строку к дате: запрос против встроенного языка

Сначала полезно понять границы. Что из этого реально работает в тексте запроса, а что — только в модуле.

ИнструментГде работаетЧто делаетОграничение
ДАТАВРЕМЯ(2024, 1, 31)ЗапросЛитерал датыАргументы — только числовые константы, не поля и не параметры
НАЧАЛОПЕРИОДА, КОНЕЦПЕРИОДАЗапросГраницы дня, месяца, годаПринимают дату, а не строку
ДОБАВИТЬКДАТЕ, РАЗНОСТЬДАТЗапросАрифметика датТоже только тип Дата
ПОДСТРОКА, + для строкЗапросВырезать и переставить части строкиРезультат остаётся строкой
Дата("20240131")КодСтрока → датаФормат строго ГГГГММДДччммсс
Дата(2024, 1, 31)КодДата из чиселНужны числа, а не строки
XMLЗначение(Тип("Дата"), Стр)КодРазбор ISO-форматаСтрока вида 2024-01-31T10:15:00
ПрочитатьДатуJSON(Стр, ФорматДатыJSON.ISO)КодРазбор даты из JSONТолько поддерживаемые форматы JSON

ВЫРАЗИТЬ(... КАК ДАТА) не решает задачу: приводить строку к дате эта операция не умеет. Её место — уточнение ссылочных типов и обрезка строк неограниченной длины, например ВЫРАЗИТЬ(Комментарий КАК СТРОКА(200)).

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

Правило простое: всё, что требует настоящего типа Дата, делайте в модуле. Запрос получает уже готовую дату через параметр или временную таблицу.

Как разобрать нестандартный формат строки с датой

Функция Дата() понимает ровно один формат — цифры подряд: 20240131 или 20240131101500. Строку 31.01.2024 она не примет и выдаст ошибку Преобразование значения к типу Дата не может быть выполнено. Для остальных форматов строку сначала разбирают на части.

Формат ДД.ММ.ГГГГ и ДД.ММ.ГГ

Самый частый случай после загрузки из Excel. Функция ниже разбирает строку по точкам, обрабатывает двузначный год и не падает на мусоре:

Функция СтрокаДДММГГГГВДату(Знач Стр) Экспорт

    Стр = СокрЛП(Стр);
    Части = СтрРазделить(Стр, ".", Ложь);
    Если Части.Количество() <> 3 Тогда
        Возврат Дата(1, 1, 1);
    КонецЕсли;

    Попытка
        День  = Число(Части[0]);
        Месяц = Число(Части[1]);
        Год   = Число(Части[2]);
        Если Год < 100 Тогда
            Год = 2000 + Год;
        КонецЕсли;
        Возврат Дата(Год, Месяц, День);
    Исключение
        Возврат Дата(1, 1, 1);
    КонецПопытки;

КонецФункции

Пустая дата 01.01.0001 на выходе — признак того, что строку разобрать не удалось. Такие строки стоит вывести отдельным списком и показать пользователю, а не молча пропустить.

Другие форматы, которые встречаются на практике

Пример строкиОткуда приходитКак получить дату
20240131Выгрузки, банковские файлыДата(Стр)
2024-01-31T10:15:00XML, веб-сервисыXMLЗначение(Тип("Дата"), Стр)
2024-01-31CSV, APIДата(СтрЗаменить(Стр, "-", ""))
31.01.2024 10:15Excel, ручной вводОтделить время по пробелу, дату разобрать функцией выше, время прибавить в секундах
45322Ячейка Excel, прочитанная как числоДата(1899, 12, 30) + Число(Стр) * 86400
1706659200Unix-время из APIДата(1970, 1, 1) + Число(Стр), результат в UTC

Проверим пример из Excel. Число 45322 — это количество дней от 30.12.1899. Умножаем на 86 400 секунд в сутках и прибавляем к начальной дате: получается 31.01.2024. Для Unix-времени 1706659200 выйдет 31.01.2024 00:00:00 по UTC. По московскому времени это уже 03:00, поэтому при необходимости прибавьте 3 часа (10 800 секунд).

Даты в формате ММ/ДД/ГГГГ из американских систем легко принять за ДД/ММ/ГГГГ. Строка 03/04/2024 читается и как 3 апреля, и как 4 марта. Формат уточняйте у источника данных, угадывать по первым строкам опасно.

Примеры запросов, где дата приходит строкой

Пример 1. Строка с формы превращается в параметр

Пользователь ввёл период строками 01.01.2024 и 31.01.2024. Приводим их к датам в коде и правим конец периода, иначе документы последнего дня потеряются:

ДатаНач = СтрокаДДММГГГГВДату(СтрокаНачала);
ДатаКон = СтрокаДДММГГГГВДату(СтрокаКонца);

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

Строка 31.01.2024 превращается в 31.01.2024 00:00:00. Документ, проведённый в 14:30 того же дня, в условие <= &ДатаКон уже не попадёт. КОНЕЦПЕРИОДА(&ДатаКон, ДЕНЬ) сдвигает границу на 23:59:59.

Пример 2. Строковый реквизит в формате ГГГГММДД

Если даты в строковом поле записаны как 20240131, их можно сравнивать без преобразования. Порядок символов совпадает с хронологическим: "20240131" < "20240201". Параметры передаём тоже строками:

"ВЫБРАТЬ
|    Дог.Ссылка,
|    Дог.ДатаСтрокой
|ИЗ
|    Справочник.ДоговорыКонтрагентов КАК Дог
|ГДЕ
|    Дог.ДатаСтрокой >= &НачСтрокой
|    И Дог.ДатаСтрокой <= &КонСтрокой"

Запрос.УстановитьПараметр("НачСтрокой", Формат(ДатаНач, "ДФ=yyyyMMdd"));
Запрос.УстановитьПараметр("КонСтрокой", Формат(ДатаКон, "ДФ=yyyyMMdd"));

ДатаСтрокой здесь — условное имя строкового реквизита, подставьте своё.

С форматом ДД.ММ.ГГГГ этот приём не работает : "31.01.2024" окажется «больше» "01.02.2024". Тогда части переставляют прямо в запросе:

ГДЕ
    ПОДСТРОКА(Дог.ДатаСтрокой, 7, 4)
        + ПОДСТРОКА(Дог.ДатаСтрокой, 4, 2)
        + ПОДСТРОКА(Дог.ДатаСтрокой, 1, 2) >= &НачСтрокой

Работает, но только при строгом формате с ведущими нулями. Строка 1.2.2024 сломает позиции, и такая запись выпадет из выборки.

Пример 3. Временная таблица соответствия «строка → дата»

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

ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Стр", Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(20)));
ТЗ.Колонки.Добавить("Дата", Новый ОписаниеТипов("Дата", , , Новый КвалификаторыДаты(ЧастиДаты.Дата)));

Для Каждого Значение Из УникальныеСтроки Цикл
    НоваяСтрока = ТЗ.Добавить();
    НоваяСтрока.Стр  = Значение;
    НоваяСтрока.Дата = СтрокаДДММГГГГВДату(Значение);
КонецЦикла;

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

Типы колонок обязательны. Без ОписаниеТипов запрос к таблице значений завершится ошибкой вида Тип не может быть выбран в запросе.

Скорость запросов с датами-строками

Пока таблица маленькая, разницы не видно. На миллионе записей она становится заметной. Главные приёмы:

  1. Не оборачивайте индексированное поле в функцию. Условие ГОД(Док.Дата) = 2024 или ПОДСТРОКА(Поле, 7, 4) = "2024" заставляет СУБД вычислить выражение для каждой строки. Индекс при этом не используется. Пишите диапазоном: Док.Дата МЕЖДУ &Нач И &Кон.
  2. Передавайте даты параметрами, а не склейкой текста. Текст запроса с параметром одинаков при каждом вызове, и СУБД может повторно использовать план выполнения. Текст со вклеенной датой каждый раз новый.
  3. Сначала сузьте выборку, потом разбирайте строки. В примере 3 сначала отберите записи по другим условиям (организация, вид договора), а потом соединяйте с таблицей дат.
  4. Индексируйте строковый реквизит в формате ГГГГММДД. У реквизита в Конфигураторе есть свойство «Индексировать». Сравнение по диапазону строк использует такой индекс, а перестановка через ПОДСТРОКА — нет.
  5. Исправьте тип в источнике. Если поле с датой нужно постоянно, добавьте реквизит типа Дата и заполните его обработкой один раз. Все остальные приёмы — обходные пути.

Заполнение нового реквизита по всей базе — массовая запись объектов. Сделайте резервную копию и запускайте обработку вне рабочего времени: при записи срабатывают подписки и проверки, а пользователи в это время могут получать блокировки.

Даты-строки в СКД, обменах и на разных СУБД

Отчёты на СКД

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

Обратная задача — показать дату строкой в нужном формате. В тексте запроса функции Формат нет. Её используют в вычисляемом поле СКД: выражение Формат(Дата, "ДФ=dd.MM.yyyy"). Ещё проще — задать формат в оформлении поля, тогда тип Дата сохранится для сортировки и группировки.

Загрузка и обмен данными

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

Файловая база, MS SQL и PostgreSQL

Для запросов 1С разницы нет: язык запросов одинаков для любой СУБД. Различия появляются, только если кто-то читает таблицы базы напрямую SQL-запросом в обход платформы. В базах на MS SQL, созданных со смещением дат 2000, даты хранятся с прибавленными 2000 годами. Сравнение строки '2024-01-31' с таким полем в прямом SQL даст неверный результат. В запросах 1С этого эффекта нет, платформа пересчитывает даты сама.

Типичные ошибки с датой-строкой в запросе

Диагностика: выберите симптом

Частые вопросы

Можно ли в запросе 1С получить строку из даты?
Напрямую нет: функции Формат в языке запросов нет. Можно получить числа через ГОД(), МЕСЯЦ() и ДЕНЬ(), а строку сформировать в коде после выполнения запроса или в вычисляемом поле СКД.
Как задать пустую дату в запросе?
Литералом ДАТАВРЕМЯ(1, 1, 1) или параметром со значением Дата(1, 1, 1). Пустая строка вместо пустой даты условие не выполнит.
Почему ВЫРАЗИТЬ(Поле КАК ДАТА) не превращает строку в дату?
ВЫРАЗИТЬ в запросе не выполняет преобразование строки в дату. Его используют для уточнения составного типа и ограничения длины строк.
Учитывает ли Дата() часовой пояс?
Нет, функция просто собирает дату из цифр строки. Смещение для UTC и Unix-времени прибавляйте сами, например 10 800 секунд для Москвы.
Что быстрее: временная таблица соответствия или ПОДСТРОКА в условии?
На больших объёмах обычно временная таблица: строки разбираются один раз, а условие по дате идёт по готовому полю. ПОДСТРОКА вычисляется для каждой строки таблицы.