При разработке в среде 1С:Предприятие программисты часто сталкиваются с ситуацией, когда данные, упорядоченные в запросе, меняют свой порядок после выгрузки в Таблицу Значений (ТЗ) и повторной сортировки методом Сортировать(). Особенно ярко эта проблема проявляется на строковых кодах справочников, содержащих дефисы, пробелы или другие спецсимволы (например, 00-005 и 000056).
В этой статье мы подробно разберем причины такого поведения систем, выясним разницу в алгоритмах сравнения строк СУБД и платформы 1С, а также рассмотрим практические способы решения этой проблемы для достижения идентичной сортировки.
Представим, что у нас есть набор текстовых кодов. Если мы упорядочим их в запросе 1С (который транслируется и выполняется на стороне СУБД MS SQL Server), мы получим один результат. Если же мы выгрузим эти данные в Таблицу Значений и применим к ней метод ТЗ.Сортировать("Код Возр"), порядок строк изменится.
Проанализируем тестовый набор данных:
При сортировке в Запросе (на стороне СУБД SQL) результат будет следующим:
При сортировке в Таблице Значений (на стороне платформы 1С) результат изменится:
Давайте выясним причину, почему один и тот же список данных распределяется по разным правилам.
Большинство баз данных 1С работают под управлением MS SQL Server или PostgreSQL. Для строковых типов данных (в SQL это тип nvarchar, используемый для хранения текстовых реквизитов 1С) СУБД применяет языковые стандарты сортировки (Collation), например, Cyrillic_General_CI_AS.
Согласно стандартам операционных систем Windows и Unix, при первом проходе сравнения строк используется алгоритм словесной сортировки (Word Sort). В этом режиме такие символы пунктуации, как дефис (-), тире или пробел, практически игнорируются системой.
Они учитываются лишь во вторую очередь, когда буквенно-цифровые части строк абсолютно одинаковы (например, чтобы определить взаимный порядок слов "co-operation" и "cooperation").
Если мы уберем «игнорируемые» дефисы из нашего примера, то СУБД будет сравнивать строки следующим образом:
00-005 виртуально превращается в 00005000056 остается 000056000056094 остается 00005609400-006 превращается в 0000600-009215 превращается в 00009215Сравнивая эти виртуальные значения посимвольно, СУБД выстраивает их в абсолютно логичную цепочку: сначала идет 00005 (оригинал: 00-005), затем 000056, далее 000056094, затем 00006 (оригинал: 00-006) и так далее. В итоге дефисы оказываются разбросанными по всему списку.
В отличие от СУБД, метод платформы ТЗ.Сортировать() работает исключительно в оперативной памяти компьютера на уровне сервера приложений или тонкого клиента 1С. Платформа выполняет посимвольную сортировку (String Sort) на основе числовых кодов символов в таблице Unicode.
Давайте сопоставим коды ключевых символов из нашего примера:
"-" имеет десятичный код 45 в Unicode."0" имеет десятичный код 48 в Unicode.Поскольку сравнение идет строго слева направо посимвольно:
"00"."00-005" стоит дефис (код 45)."000056" стоит ноль (код 48).Так как 45 меньше 48, платформа однозначно определяет, что строка с дефисом на третьем месте "меньше" строки с цифрой. По этой причине все строки вида 00-... гарантированно поднимаются в самый верх списка ТЗ, опережая любые строки вида 000....
Именно поэтому использование объекта СравнениеЗначений при сортировке Таблицы Значений не решает проблему: этот объект лишь помогает сопоставить типы данных, но базовый посимвольный алгоритм сравнения строк внутри 1С остается неизменным.
Рассмотрим два основных практических сценария решения этой проблемы в зависимости от того, какой именно порядок сортировки вам необходим в результате.
Если вам критически важно сохранить «умную» языковую сортировку СУБД (Word Sort) внутри Таблицы Значений, нам потребуется сымитировать поведение SQL-сервера. Для этого мы временно уберем дефисы из сравнения строк.
Разберем по шагам, как это реализовать программно:
СтрЗаменить().Посмотрим на пример готового кода:
// Предположим, что ТаблицаДанных уже заполнена результатами запроса
// Добавляем служебную колонку для имитации Word Sort
ИмяСлужебнойКолонки = "КодДляСортировки";
ТаблицаДанных.Колонки.Добавить(ИмяСлужебнойКолонки, Новый ОписаниеТипов("Строка"));
// Заполняем временную колонку очищенными строками
Для Каждого СтрокаТЗ Из ТаблицаДанных Цикл
// Исключаем дефисы из сравнения, как это делает SQL
СтрокаТЗ[ИмяСлужебнойКолонки] = СтрЗаменить(СтрокаТЗ.Код, "-", "");
КонецЦикла;
// Сортируем таблицу по подготовленному представлению
ТаблицаДанных.Сортировать(ИмяСлужебнойКолонки + " Возр");
// Удаляем временную колонку перед выводом или дальнейшей обработкой
ТаблицаДанных.Колонки.Удалить(ИмяСлужебнойКолонки);
Этот простой метод позволяет со стопроцентной точностью воспроизвести в оперативной памяти порядок строк, сформированный базой данных.
Если вам, наоборот, требуется строгое посимвольное упорядочивание (где спецсимволы и дефисы идут в начале списка по кодам ASCII/Unicode), то полагаться на сортировку внутри SQL-запроса нельзя.
В языке запросов 1С отсутствует поддержка конструкции COLLATE для явного указания бинарного порядка сравнения на уровне СУБД (например, Cyrillic_General_BIN). Поэтому единственным надежным способом синхронизации будет отказ от упорядочивания внутри СУБД.
Разберем по шагам этот алгоритм:
УПОРЯДОЧИТЬ ПО из текста запроса. Это также повысит производительность работы СУБД, избавив её от лишней операции.Выгрузить().Посмотрим на пример реализации:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Организации.Код КАК Код,
| Организации.Ссылка КАК Ссылка
|ИЗ
| Справочник.Организации КАК Организации"; // УПОРЯДОЧИТЬ ПО удалено для унификации
РезультатЗапроса = Запрос.Выполнить();
ТаблицаДанных = РезультатЗапроса.Выгрузить();
// Сортируем строго по символьным кодам в памяти 1С
ТаблицаДанных.Сортировать("Код Возр");
Используя этот подход, вы получите предсказуемый посимвольный результат, где все коды с дефисами гарантированно окажутся в начале результирующего списка.