В процессе работы с платформой 1С мы часто сталкиваемся с необходимостью отображения и сортировки данных в динамических списках. Особенно сложной задача становится, когда поле списка, которое должно содержать дату, на самом деле имеет составной тип (например, Строка и Дата), и в нем хранится дата в строковом формате (например, "01.09.2022"). Стандартные механизмы сортировки в таком случае могут работать некорректно, поскольку платформа будет воспринимать данные как обычные строки, сортируя их лексикографически, а не хронологически. Разберем, как эффективно решить эту проблему, не прибегая к дорогостоящей реструктуризации базы данных.
Одним из наиболее гибких и мощных способов решения задачи является формирование собственного произвольного запроса для динамического списка. Этот подход позволяет нам "на лету" преобразовывать строковое представление даты в фактический тип Дата непосредственно в запросе, что дает возможность динамическому списку корректно выполнять сортировку.
Активируем произвольный запрос:
Для начала, нам потребуется перейти в свойства динамического списка на форме. Найдем свойство Основная таблица и вместо выбора существующей таблицы, мы установим флаг Произвольный запрос. Это действие позволит нам вручную написать SQL-подобный запрос, который будет формировать данные для списка.
Создаем запрос с преобразованием:
После активации произвольного запроса откроется конструктор запросов или поле для ввода текста запроса. Здесь нам необходимо извлечь исходные данные и применить к полю, хранящему дату в строковом формате, функции преобразования. Основная сложность заключается в том, что в запросах 1С нет прямой функции "СтрокаВДату" для произвольных строковых форматов. Мы должны будем "разобрать" строку на составляющие (день, месяц, год) и затем собрать из них полноценное значение даты.
Предположим, наша дата хранится в строковом формате "ДД.ММ.ГГГГ". Мы можем использовать функции ПОДСТРОКА и ДАТАВРЕМЯ для выполнения преобразования.
Важный момент: Если в поле могут содержаться не только даты, но и другие строковые значения, нам понадобится дополнительная логика для их обработки, чтобы избежать ошибок преобразования. Мы можем использовать конструкцию ВЫБОР КОГДА ТОГДА ИНАЧЕ для проверки формата строки или ее типа.
Пример кода для преобразования строки "ДД.ММ.ГГГГ" в дату в запросе:
Рассмотрим, как будет выглядеть такой запрос. В этом примере мы предполагаем, что строковое поле с датой называется ПолеСоСтроковойДатой в таблице ОсновнаяТаблица. Мы будем формировать новое поле ДатаДляСортировки, которое уже будет иметь тип Дата.
ВЫБРАТЬ
ОсновнаяТаблица.Ссылка,
ОсновнаяТаблица.Поле1,
ОсновнаяТаблица.Поле2,
ВЫБОР
КОГДА ТИПЗНАЧЕНИЯ(ОсновнаяТаблица.ПолеСоСтроковойДатой) = ТИП(Строка)
И ОсновнаяТаблица.ПолеСоСтроковойДатой ПОДОБНО "__.__.____"
ТОГДА ДАТАВРЕМЯ(
ЧИСЛО(ПОДСТРОКА(ОсновнаяТаблица.ПолеСоСтроковойДатой, 7, 4)), // Год
ЧИСЛО(ПОДСТРОКА(ОсновнаяТаблица.ПолеСоСтроковойДатой, 4, 2)), // Месяц
ЧИСЛО(ПОДСТРОКА(ОсновнаяТаблица.ПолеСоСтроковойДатой, 1, 2)) // День
)
КОГДА ТИПЗНАЧЕНИЯ(ОсновнаяТаблица.ПолеСоСтроковойДатой) = ТИП(Дата)
ТОГДА ОсновнаяТаблица.ПолеСоСтроковойДатой
ИНАЧЕ ДАТАВРЕМЯ(1, 1, 1) // Значение по умолчанию для некорректных/не-датовых строк
КОНЕЦ КАК ДатаДляСортировки,
ОсновнаяТаблица.ПолеСоСтроковойДатой
ИЗ
ОсновнаяТаблица КАК ОсновнаяТаблица
В этом запросе мы выполняем следующие действия:
ВЫБОР КОГДА ТОГДА ИНАЧЕ, чтобы сначала проверить тип значения в поле. Если это уже Дата, мы просто ее берем.ПОДОБНО), то мы используем ПОДСТРОКА для извлечения частей даты:
ПОДСТРОКА(ОсновнаяТаблица.ПолеСоСтроковойДатой, 7, 4) — извлекает год (4 символа, начиная с 7-й позиции).ПОДСТРОКА(ОсновнаяТаблица.ПолеСоСтроковойДатой, 4, 2) — извлекает месяц (2 символа, начиная с 4-й позиции).ПОДСТРОКА(ОсновнаяТаблица.ПолеСоСтроковойДатой, 1, 2) — извлекает день (2 символа, начиная с 1-й позиции).ЧИСЛО.ДАТАВРЕМЯ, которая создает полноценное значение даты.NULL, в зависимости от требуемой логики.После этого запроса, в динамическом списке мы будем работать с полем ДатаДляСортировки, которое имеет тип Дата и по которому будет выполняться корректная сортировка.
Рекомендации по производительности:
При использовании произвольных запросов важно помнить о производительности. Избегайте слишком сложных преобразований или большого количества соединений, если таблица содержит миллионы записей. Для полей, используемых в сортировке, желательно иметь индексы, но в нашем случае преобразование выполняется на лету, поэтому индекс по исходной строке не поможет напрямую. Однако, если у вас есть возможность использовать функции БСП (Библиотеки стандартных подсистем), такие как СтрокаВДату (которая может быть доступна в некоторых конфигурациях и версиях платформы для более стандартных форматов), это может упростить запрос, но требует проверки ее наличия и корректности работы в запросе.
Второй подход направлен не на исправление данных при их чтении, а на предотвращение некорректного хранения в будущем. Если вы можете определить место в конфигурации, где происходит запись данных в проблемный реквизит, мы можем перехватить этот момент и выполнить конвертацию строкового представления даты в фактический тип Дата перед записью.
Определение места записи данных:
Мы начинаем с анализа конфигурации. Нам необходимо найти событие, которое срабатывает при записи объекта, содержащего наше поле. Чаще всего это метод ПриЗаписи() или ПередЗаписью() объекта (справочника, документа, регистра сведений и т.д.). Также данные могут записываться через обработку или в результате какого-либо обмена данными. Идентифицируем код, который присваивает значение нашему составному реквизиту.
Реализация конвертации:
После того как мы нашли подходящее место, мы вставляем код, который будет проверять тип записываемого значения. Если это строка, мы пытаемся преобразовать ее в дату и присваиваем уже преобразованное значение реквизиту. Если реквизит имеет составной тип Строка, Дата, то при попытке присвоить ему значение типа Дата, платформа корректно его сохранит как дату.
Рассмотрим пример кода, который можно разместить в обработчике события ПередЗаписью() объекта конфигурации (например, Справочника или Документа).
&НаСервере
Процедура ПередЗаписью(Отказ, ПараметрыЗаписи)
// Предполагаем, что наш реквизит называется "ДатаВПоле" и может быть строкой или датой.
// Нам нужно убедиться, что если это строка, то она преобразуется в дату,
// если это возможно и необходимо для корректной сортировки.
Если ТипЗнч(ЭтотОбъект.ДатаВПоле) = Тип("Строка") Тогда
// Проверяем формат строки "ДД.ММ.ГГГГ"
Если СтрДлина(ЭтотОбъект.ДатаВПоле) = 10 И СтрНайти(ЭтотОбъект.ДатаВПоле, ".") = 3 И СтрНайти(ЭтотОбъект.ДатаВПоле, ".", 4) = 6 Тогда
Попытка
// Используем стандартную функцию ПреобразоватьСтрокуВДату если она есть
// или парсим вручную
СтрокаДаты = ЭтотОбъект.ДатаВПоле;
День = Число(Сред(СтрокаДаты, 1, 2));
Месяц = Число(Сред(СтрокаДаты, 4, 2));
Год = Число(Сред(СтрокаДаты, 7, 4));
НоваяДата = Дата(Год, Месяц, День);
// Присваиваем уже преобразованную дату реквизиту
ЭтотОбъект.ДатаВПоле = НоваяДата;
Исключение
// Обработка ошибки преобразования, если строка не является корректной датой
// Возможно, выдать сообщение пользователю или записать в журнал регистрации
Сообщить("Не удалось преобразовать строку '" + ЭтотОбъект.ДатаВПоле + "' в дату. Ошибка: " + ОписаниеОшибки());
// Можно Отказ = Истина; если некорректные даты недопустимы.
КонецПопытки;
КонецЕсли;
КонецЕсли;
// ... Другая логика перед записью ...
КонецПроцедуры
В приведенном примере мы используем функции встроенного языка 1С:
ТипЗнч() для проверки текущего типа значения реквизита.СтрДлина(), СтрНайти(), Сред() для разбора строкового представления даты.Число() для преобразования частей даты в числовой формат.Дата() для создания значения типа Дата из года, месяца и дня.Попытка...Исключение для корректной обработки возможных ошибок преобразования, если строка оказалась невалидной датой.После такого изменения, все новые записи (или модифицированные старые записи при их перезаписи) будут хранить даты корректно, что позволит динамическому списку сортировать их без дополнительных ухищрений в запросе.
Преимущества такого подхода:
Основное преимущество заключается в том, что мы постепенно "очищаем" данные. Новые данные будут записываться в правильном формате, а старые, при их изменении, также будут конвертироваться. Это не требует глобальной реструктуризации и позволяет добиться желаемого поведения сортировки для большинства данных со временем. Однако, стоит помнить, что существующие некорректные записи останутся строками до их явного изменения и перезаписи.
Платформа 1С имеет свои особенности при работе с сортировкой полей составного типа. По умолчанию, интерактивное управление сортировкой (когда пользователь кликает на заголовок колонки) доступно только для тех полей, упорядочивание по которым будет эффективным и однозначным. Если реквизит метаданных содержит в своем составе примитивный тип Строка, то платформа может выполнять лексикографическую сортировку, которая некорректна для дат. Также сортировка не может выполняться по полям типа Строка неограниченной длины.
Даже без использования произвольного запроса, существует возможность программно установить порядок сортировки для динамического списка. Мы можем использовать свойство Порядок у элемента формы ДинамическийСписок для того, чтобы задать нужный порядок при открытии формы или по какому-либо другому событию. Например, если мы хотим отсортировать по полю ДатаВПоле, которое является составным, но мы знаем, что оно чаще всего содержит дату, мы можем указать:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Получаем элемент формы, который является динамическим списком
ЭлементСписка = Элементы.МойДинамическийСписок;
// Устанавливаем порядок сортировки по полю "ДатаВПоле" по возрастанию
ЭлементСписка.Порядок = "ДатаВПоле ВОЗР";
// Если поле "ДатаВПоле" уже преобразуется в запросе в "ДатаДляСортировки",
// то порядок будет "ДатаДляСортировки ВОЗР"
КонецПроцедуры
Однако, этот метод не решает проблему некорректной лексикографической сортировки, если в самом поле данные по-прежнему остаются строками. Он лишь указывает платформе, по какому полю сортировать. Если тип данных внутри поля не соответствует ожидаемому (т.е., если даты записаны как строки), то и программная сортировка будет выполнять строковую сортировку. Поэтому, данное решение эффективно только в сочетании с одним из вышеуказанных методов преобразования типа данных (в запросе или при записи).
Мы рассмотрели два эффективных способа решения проблемы без реструктуризации. Однако, если бы существовала возможность, наиболее правильным и производительным решением было бы изменение способа хранения даты с типа "Строка" на тип "Дата" непосредственно в метаданных базы данных. Это исключило бы необходимость преобразования типов при каждом чтении и сортировке, значительно упростило бы логику и повысило производительность системы в целом. В реальной жизни, как мы видим из обсуждения, такое изменение часто бывает невозможно из-за уже внедренных систем, большого объема данных или других организационных причин. Но всегда полезно помнить, какой подход является идеальным с точки зрения проектирования базы данных.
В заключение, выбирая между произвольным запросом и конвертацией при записи, мы обычно руководствуемся текущим состоянием данных и возможностями по модификации конфигурации. Произвольный запрос – это быстрый способ получить корректную сортировку для уже существующих данных. Конвертация при записи – это более фундаментальный подход, который решает проблему для будущих данных и постепенно "чистит" старые.