Приведение значений к типу Дата в языке запросов 1С: что умеет ВЫРАЗИТЬ и где нужны другие приёмы
Содержание
ВЫРАЗИТЬ(Поле КАК ДАТА) в запросе 1С оставляет от поля только значения типа Дата: если в поле лежит дата — она и вернётся, если что-то другое (строка, число, ссылка) — вернётся NULL. Это приведение типа, а не разбор текста. Превратить строку «15.01.2024» в дату оператор Выразить не умеет: для этого дату собирают из чисел функцией ДОБАВИТЬКДАТЕ или преобразуют строку уже в коде, после выполнения запроса.
На практике ВЫРАЗИТЬ КАК ДАТА нужен в трёх ситуациях: поле составного типа (дополнительные реквизиты, значения свойств, субконто), временная таблица, куда должна попасть колонка строго типа Дата, и объединение запросов, где тип колонки «расплывается». Ниже — синтаксис, рабочие примеры для каждого случая и ошибки, на которых чаще всего спотыкаются.
Как устроен оператор ВЫРАЗИТЬ для типа Дата
Общий вид оператора:
ВЫРАЗИТЬ(<Выражение> КАК <Тип>)
В качестве типа указывают БУЛЕВО, ЧИСЛО, СТРОКА, ДАТА или имя ссылочной таблицы (Справочник.Контрагенты, Документ.РеализацияТоваровУслуг). У строки и числа есть уточнения — СТРОКА(100), ЧИСЛО(15, 2). У ДАТА уточнений нет: состав даты (только дата, только время, дата и время) в ВЫРАЗИТЬ не задаётся.
Что происходит при выполнении:
- 📅 значение уже имеет тип Дата — возвращается без изменений, вместе со временем;
- 🚫 значение другого типа — результат
NULL; - ⚪ значение
NULL— остаётсяNULL.
Отсюда главное правило: ВЫРАЗИТЬ ничего не вычисляет и не распознаёт. Он сужает тип. Если поле и так описано в метаданных как Дата (например, Документ.Дата или реквизит ДатаОплаты с типом Дата), оборачивать его в ВЫРАЗИТЬ бессмысленно — запрос станет только длиннее.
Ключевые слова языка запросов регистронезависимы: ВЫРАЗИТЬ, Выразить и CAST в английском синтаксисе равнозначны. В примерах ниже — заглавные буквы, так их проще отличить от имён полей.
Строка с датой в запросе: почему ВЫРАЗИТЬ не подходит и как быть
Самый частый вопрос по теме — как в запросе выразить строку как дату. Например, во внешней таблице или в реквизите типа Строка хранится «20240115» или «15.01.2024». Запрос вида ВЫРАЗИТЬ(Т.ДатаСтрокой КАК ДАТА) не даст нужной даты: строка — не дата, и приведения текста к дате язык запросов не предусматривает.
Рабочих путей три.
Способ 1. Преобразовать строку в коде после запроса
Самый надёжный вариант. Запрос выбирает строку как есть, а встроенный язык превращает её в дату. Конструктор Дата() принимает строку из цифр в порядке «ГГГГММДДччммсс»:
// Строка вида «20240115»
ДатаЗначение = Дата(Выборка.ДатаСтрокой + "000000");
// Строка вида «15.01.2024»
С = Выборка.ДатаСтрокой;
ДатаЗначение = Дата(Сред(С, 7, 4) + Сред(С, 4, 2) + Сред(С, 1, 2) + "000000");
Если в данных бывает мусор (пустые строки, «31.02.2024», лишние пробелы), оберните преобразование в Попытка … Исключение. Иначе первая же кривая строка остановит обработку с ошибкой преобразования к типу Дата.
Попытка
ДатаЗначение = Дата(Сред(С, 7, 4) + Сред(С, 4, 2) + Сред(С, 1, 2) + "000000");
Исключение
ДатаЗначение = Дата(1, 1, 1); // пустая дата
КонецПопытки;
Для строк в формате XML («2024-01-15T00:00:00») подходит XMLЗначение(Тип("Дата"), С).
Способ 2. Подготовить таблицу значений и передать её в запрос
Если дата нужна внутри запроса — для отбора или соединения — разберите строки заранее. Сформируйте ТаблицаЗначений с колонкой типа Дата, заполните её в коде и передайте параметром во временную таблицу:
Запрос.УстановитьПараметр("ВнешниеДанные", ТаблицаСДатами);
Запрос.Текст =
"ВЫБРАТЬ
| Т.Код КАК Код,
| Т.ДатаДокумента КАК ДатаДокумента
|ПОМЕСТИТЬ ВТ_Внешние
|ИЗ
| &ВнешниеДанные КАК Т";
Колонке таблицы значений задайте тип явно: Новый ОписаниеТипов("Дата"). Без описания типа запрос не сможет поместить её во временную таблицу.
Способ 3. Собрать дату из чисел прямо в запросе
Если год, месяц и день хранятся отдельными числовыми полями, дату можно построить без кода. Функция ДАТАВРЕМЯ() тут не поможет: в запросе она принимает только числовые константы, а не поля. Поэтому берут пустую дату и прибавляют к ней периоды:
ВЫБРАТЬ
ДОБАВИТЬКДАТЕ(
ДОБАВИТЬКДАТЕ(
ДОБАВИТЬКДАТЕ(ДАТАВРЕМЯ(1, 1, 1), ГОД, Т.Год - 1),
МЕСЯЦ, Т.Месяц - 1),
ДЕНЬ, Т.День - 1) КАК СобраннаяДата
ИЗ
ВТ_Периоды КАК Т
Порядок важен: сначала год, потом месяц, потом день. Проверка на примере: 01.01.0001 + 2023 года = 01.01.2024, + 0 месяцев = 01.01.2024, + 14 дней = 15.01.2024 при День = 15.
Поля составного типа: ВЫРАЗИТЬ КАК ДАТА в деле
Вот где оператор действительно нужен. Возьмём дополнительные реквизиты: у колонки Значение составной тип — там бывают строки, числа, ссылки на справочники и даты. Чтобы отобрать контрагентов, у которых дополнительный реквизит «Дата договора» позже 1 января, пишут так:
ВЫБРАТЬ
ДопРек.Ссылка КАК Контрагент,
ВЫРАЗИТЬ(ДопРек.Значение КАК ДАТА) КАК ДатаДоговора
ИЗ
Справочник.Контрагенты.ДополнительныеРеквизиты КАК ДопРек
ГДЕ
ДопРек.Свойство = &СвойствоДатаДоговора
И ВЫРАЗИТЬ(ДопРек.Значение КАК ДАТА) >= &НачалоПериода
Что даёт ВЫРАЗИТЬ в этом примере:
- Колонка
ДатаДоговорав результате имеет тип Дата, а не составной. С ней корректно работают сортировка, группировка и вывод в отчёт. - Сравнение
>= &НачалоПериодаидёт по датам, а строки и ссылки из колонкиЗначениеотсекаются — для них ВЫРАЗИТЬ вернулNULL, а сравнение сNULLложно. - При помещении во временную таблицу колонка занимает меньше места и её можно проиндексировать.
Похожая ситуация — субконто в регистре бухгалтерии. Если вид субконто имеет тип Дата (так бывает в пользовательских видах субконто), значение достают через ВЫРАЗИТЬ(Хозрасчетный.Субконто1 КАК ДАТА).
Как проверить тип, не теряя строки
Иногда нужна не сама дата, а понимание, сколько записей содержат дату. Для этого служит пара ТИПЗНАЧЕНИЯ и ТИП:
ВЫБОР
КОГДА ТИПЗНАЧЕНИЯ(ДопРек.Значение) = ТИП(ДАТА)
ТОГДА ВЫРАЗИТЬ(ДопРек.Значение КАК ДАТА)
ИНАЧЕ ДАТАВРЕМЯ(1, 1, 1)
КОНЕЦ КАК ДатаИлиПусто
Так в колонке не будет NULL: вместо него — пустая дата 01.01.0001. Короче то же самое записывается через ЕСТЬNULL(ВЫРАЗИТЬ(ДопРек.Значение КАК ДАТА), ДАТАВРЕМЯ(1, 1, 1)).
Объединение запросов
При ОБЪЕДИНИТЬ ВСЕ тип колонки берётся из всех частей. Если в первой части поле — дата, а во второй вы подставили NULL или строку-заглушку, тип колонки становится составным. Подставляйте во вторую часть ДАТАВРЕМЯ(1, 1, 1) и, если поле приходит из составного реквизита, оборачивайте его в ВЫРАЗИТЬ(… КАК ДАТА) в обеих частях.
Формат даты, параметры и литералы
В языке запросов нет функции форматирования даты: Формат() — функция встроенного языка, в тексте запроса её не вызвать. Внешний вид даты задают уже после выборки.
| Задача | Где решать | Как |
|---|---|---|
| Вывести «15.01.2024» | Код | Формат(Д, "ДФ=dd.MM.yyyy") |
| Вывести «15 января 2024 г.» | Код | Формат(Д, "ДЛФ=DD") |
| Строка «20240115» для файла обмена | Код | Формат(Д, "ДФ=yyyyMMdd") |
| Формат колонки в отчёте СКД | Схема компоновки | Параметр оформления поля «Формат» |
| Дата-константа в запросе | Запрос | ДАТАВРЕМЯ(2024, 1, 15) |
| Дата со временем в запросе | Запрос | ДАТАВРЕМЯ(2024, 1, 15, 23, 59, 59) |
Параметры с датой
Дату в запрос лучше передавать параметром, а не литералом в тексте:
Запрос.УстановитьПараметр("НачалоПериода", НачалоДня(ДатаНачала));
Запрос.УстановитьПараметр("КонецПериода", КонецДня(ДатаОкончания));
Не передавайте дату строкой: УстановитьПараметр("НачалоПериода", "01.01.2024") положит в параметр строку, и сравнение с полем типа Дата не отберёт ничего. Тип параметра должен быть Дата — Дата(2024, 1, 1) или значение из реквизита формы.
Функции, которые работают с датой внутри запроса
Когда дата уже приведена к нужному типу, с ней можно работать прямо в запросе:
- 📆
НАЧАЛОПЕРИОДА(Дата, ДЕНЬ)иКОНЕЦПЕРИОДА(Дата, МЕСЯЦ)— границы периода; - ➕
ДОБАВИТЬКДАТЕ(Дата, ДЕНЬ, 10)— сдвиг на 10 дней; - ↔️
РАЗНОСТЬДАТ(Дата1, Дата2, ДЕНЬ)— разница в днях; - 🔢
ГОД(),КВАРТАЛ(),МЕСЯЦ(),ДЕНЬ(),ДЕНЬНЕДЕЛИ()— части даты числом.
Пример: сколько дней прошло с даты договора из дополнительного реквизита до текущей даты, переданной параметром:
РАЗНОСТЬДАТ(ВЫРАЗИТЬ(ДопРек.Значение КАК ДАТА), &ТекущаяДата, ДЕНЬ) КАК ДнейСДоговора
Скорость запросов с датами
Даты чаще всего попадают в условия отбора, и именно там теряется скорость.
Не оборачивайте поле в функцию в условии ГДЕ. Условие НАЧАЛОПЕРИОДА(Док.Дата, ДЕНЬ) = &День заставляет СУБД вычислить функцию для каждой строки, и индекс по дате не используется. Тот же отбор быстрее записать диапазоном:
ГДЕ
Док.Дата МЕЖДУ &НачалоДня И &КонецДня
Границы посчитайте в коде через НачалоДня() и КонецДня().
Отбор по составному полю сужайте заранее. Условие ВЫРАЗИТЬ(ДопРек.Значение КАК ДАТА) >= &Дата по всей таблице дополнительных реквизитов перебирает все записи. Ставьте рядом условие по свойству (ДопРек.Свойство = &Свойство) — оно отсекает лишнее до сравнения дат.
Виртуальные таблицы фильтруйте параметрами. Для регистров отбор по периоду передают в параметры виртуальной таблицы (Остатки(&Дата, …), Обороты(&Начало, &Конец, …)), а не в ГДЕ после неё. Иначе регистр сначала посчитает обороты за всё время.
Индексируйте временные таблицы по дате, если по ней соединяете. Колонка, приведённая через ВЫРАЗИТЬ к типу Дата, индексируется так же, как обычная: ИНДЕКСИРОВАТЬ ПО ДатаДоговора. Для маленьких таблиц (сотни строк) индекс не нужен — выигрыша не будет.
Проверяйте скорость на копии базы с реальным объёмом данных. Запрос, который на демобазе выполняется мгновенно, на рабочей базе с миллионами записей может работать минутами.
Ошибки при приведении к дате и как их распознать
Сводка того, что ломается чаще всего, и как это выглядит со стороны.
| Симптом | Причина | Что сделать |
|---|---|---|
| Запрос возвращает пустой результат, хотя данные есть | Параметр передан строкой, а не датой | Передать Дата(2024, 1, 1), а не “01.01.2024” |
| Пропали документы последнего дня периода | Конец периода передан как 00:00:00 | Передавать КонецДня(ДатаОкончания) |
| В колонке даты вместо значений — пустые ячейки | ВЫРАЗИТЬ вернул NULL: в составном поле не дата | Проверить данные через ТИПЗНАЧЕНИЯ, заменить NULL через ЕСТЬNULL |
| Ошибка синтаксиса при ДАТАВРЕМЯ с полями | Функция в запросе принимает только константы | Собрать дату через ДОБАВИТЬКДАТЕ |
| Из строк получаются NULL | ВЫРАЗИТЬ не разбирает текст | Преобразовать строку в коде через Дата() |
| Запрос с Формат() не проходит проверку синтаксиса | Формат() есть только во встроенном языке | Форматировать после выборки или в СКД |
Отдельная ловушка — отбор по NULL. Условие ВЫРАЗИТЬ(Поле КАК ДАТА) <> ДАТАВРЕМЯ(1, 1, 1) не вернёт строки, где в поле не дата: сравнение NULL с чем угодно ложно. Если нужны все записи без заполненной даты, пишите явно: ВЫРАЗИТЬ(Поле КАК ДАТА) ЕСТЬ NULL ИЛИ ВЫРАЗИТЬ(Поле КАК ДАТА) = ДАТАВРЕМЯ(1, 1, 1).
Ещё одна мелочь, которая съедает время: часовые пояса. Даты в запросе — это даты сервера базы без пояса. Если код на клиенте подставляет ТекущаяДата(), а сервер в другом поясе, граница «сегодня» сдвигается. Для бизнес-логики используйте ТекущаяДатаСеанса() на сервере.