Строка вместо даты в запросе 1С: как привести тип и не потерять скорость
Содержание
Язык запросов 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:00 | XML, веб-сервисы | XMLЗначение(Тип("Дата"), Стр) |
2024-01-31 | CSV, API | Дата(СтрЗаменить(Стр, "-", "")) |
31.01.2024 10:15 | Excel, ручной ввод | Отделить время по пробелу, дату разобрать функцией выше, время прибавить в секундах |
45322 | Ячейка Excel, прочитанная как число | Дата(1899, 12, 30) + Число(Стр) * 86400 |
1706659200 | Unix-время из 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)));
ТЗ.Колонки.Добавить("Дата", Новый ОписаниеТипов("Дата", , , Новый КвалификаторыДаты(ЧастиДаты.Дата)));
Для Каждого Значение Из УникальныеСтроки Цикл
НоваяСтрока = ТЗ.Добавить();
НоваяСтрока.Стр = Значение;
НоваяСтрока.Дата = СтрокаДДММГГГГВДату(Значение);
КонецЦикла;
Запрос.Текст =
"ВЫБРАТЬ
| Т.Стр,
| Т.Дата
|ПОМЕСТИТЬ ВТ_Даты
|ИЗ
| &ТЗ КАК Т
|;
|ВЫБРАТЬ
| Дог.Ссылка,
| ВТ_Даты.Дата КАК ДатаДоговора
|ИЗ
| Справочник.ДоговорыКонтрагентов КАК Дог
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Даты КАК ВТ_Даты
| ПО Дог.ДатаСтрокой = ВТ_Даты.Стр
|ГДЕ
| ВТ_Даты.Дата МЕЖДУ &ДатаНач И &ДатаКон";
Запрос.УстановитьПараметр("ТЗ", ТЗ);
Типы колонок обязательны. Без ОписаниеТипов запрос к таблице значений завершится ошибкой вида Тип не может быть выбран в запросе.
Скорость запросов с датами-строками
Пока таблица маленькая, разницы не видно. На миллионе записей она становится заметной. Главные приёмы:
- Не оборачивайте индексированное поле в функцию. Условие
ГОД(Док.Дата) = 2024илиПОДСТРОКА(Поле, 7, 4) = "2024"заставляет СУБД вычислить выражение для каждой строки. Индекс при этом не используется. Пишите диапазоном:Док.Дата МЕЖДУ &Нач И &Кон. - Передавайте даты параметрами, а не склейкой текста. Текст запроса с параметром одинаков при каждом вызове, и СУБД может повторно использовать план выполнения. Текст со вклеенной датой каждый раз новый.
- Сначала сузьте выборку, потом разбирайте строки. В примере 3 сначала отберите записи по другим условиям (организация, вид договора), а потом соединяйте с таблицей дат.
- Индексируйте строковый реквизит в формате ГГГГММДД. У реквизита в Конфигураторе есть свойство «Индексировать». Сравнение по диапазону строк использует такой индекс, а перестановка через
ПОДСТРОКА— нет. - Исправьте тип в источнике. Если поле с датой нужно постоянно, добавьте реквизит типа
Датаи заполните его обработкой один раз. Все остальные приёмы — обходные пути.
Заполнение нового реквизита по всей базе — массовая запись объектов. Сделайте резервную копию и запускайте обработку вне рабочего времени: при записи срабатывают подписки и проверки, а пользователи в это время могут получать блокировки.
Даты-строки в СКД, обменах и на разных СУБД
Отчёты на СКД
В схеме компоновки параметр периода создавайте с типом Дата или СтандартныйПериод. Если параметр по ошибке получил тип Строка, пользователь увидит обычное поле ввода текста, а условие в запросе сравнит дату со строкой.
Обратная задача — показать дату строкой в нужном формате. В тексте запроса функции Формат нет. Её используют в вычисляемом поле СКД: выражение Формат(Дата, "ДФ=dd.MM.yyyy"). Ещё проще — задать формат в оформлении поля, тогда тип Дата сохранится для сортировки и группировки.
Загрузка и обмен данными
Дату из внешнего файла приводите к типу сразу при чтении, до записи в объект. Строковое значение, которое легло в реквизит типа Дата, при записи превратится в пустую дату или вызовет ошибку. Разбор лучше вынести в общую функцию и вызывать из всех обработок загрузки.
Файловая база, MS SQL и PostgreSQL
Для запросов 1С разницы нет: язык запросов одинаков для любой СУБД. Различия появляются, только если кто-то читает таблицы базы напрямую SQL-запросом в обход платформы. В базах на MS SQL, созданных со смещением дат 2000, даты хранятся с прибавленными 2000 годами. Сравнение строки '2024-01-31' с таким полем в прямом SQL даст неверный результат. В запросах 1С этого эффекта нет, платформа пересчитывает даты сама.