При проектировании регистров сведений разработчики нередко сталкиваются с задачей сохранения строковых кодов, идентификаторов или артикулов, где регистр символов имеет значение: например, "ААА", "ААа" и "АаА" должны быть тремя совершенно разными записями. Однако стандартная попытка записать такие строки в строковое измерение независимого регистра сведений приводит к ошибке дублирования ключа. Разберем подробно, почему платформа 1С ведет себя именно так и какими техническими способами можно реализовать кейсозависимое хранение данных.
Проанализируем архитектуру хранения данных в платформе 1С:Предприятие. Измерения регистра сведений формируют первичный ключ (уникальный индекс) таблицы СУБД. При создании базы данных платформа жестко задает правила сортировки и сопоставления символов (Collation) с признаком CI (Case-Insensitive), например, Cyrillic_General_CI_AS в Microsoft SQL Server или регистронезависимые правила сортировки в PostgreSQL.
Из-за этого правила на уровне базы данных действуют следующие ограничения:
"ААА", "ААа" и "ааа" являются абсолютно эквивалентными. При попытке вставить вторую запись с тем же набором символов в другом регистре СУБД возвращает ошибку нарушения уникальности первичного ключа.=, ПОДОБНО, В, условия соединения ПО) на языке запросов 1С транслируются в SQL-запрос и выполняются на стороне СУБД без учета регистра букв.Посмотрим на показательный пример в консоли запросов:
ВЫБРАТЬ
"Совпало" КАК Результат
ГДЕ
"ААА" = "ААа"
Такой запрос вернет строку "Совпало", поскольку для СУБД обе строковые константы идентичны.
Важно различать, где именно выполняется код. В отличие от языка запросов, встроенный язык 1С при работе с объектами в оперативной памяти выполняет сравнение строк с учетом регистра.
Рассмотрим пример работы с коллекциями в памяти:
ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Ключ", Новый ОписаниеТипов("Строка"));
ТЗ.Колонки.Добавить("Значение", Новый ОписаниеТипов("Строка"));
Стр = ТЗ.Добавить();
Стр.Ключ = "ААА";
Стр.Значение = "123";
Стр = ТЗ.Добавить();
Стр.Ключ = "ААа";
Стр.Значение = "4567";
// Выполняем поиск по строке в нижнем регистре
ПоискСтрок = ТЗ.НайтиСтроки(Новый Структура("Ключ", "ааа"));
КоличествоНайденных = ПоискСтрок.Количество(); // Результат будет 0
Метод НайтиСтроки(), оператор = во встроенном языке, а также поиск по ключу в объекте Соответствие чувствительны к регистру. Однако, как только данные передаются в базу данных через объект РегистрСведенийНаборЗаписей или РегистрСведенийМенеджерЗаписи, начинает действовать логика СУБД.
Разберем самый надежный и прозрачный способ: хранить в измерении регистра шестнадцатеричное представление двоичных данных исходной строки. Поскольку символы разного регистра имеют разные байтовые коды в кодировке UTF-8 (например, 'A' — это 0x41, а 'a' — 0x61), результирующая строка будет состоять исключительно из цифр и заглавных латинских букв 0-9, A-F. Такое значение будет уникальным даже в регистронезависимой СУБД.
Реализуем функции прямого и обратного преобразования:
// Функция кодирования строки в Hex-представление
Функция СтрокаВХекс(ИсходнаяСтрока)
Двоичные = ПолучитьДвоичныеДанныеИзСтроки(ИсходнаяСтрока, КодировкаТекста.UTF8, Ложь);
Возврат ВРег(ПолучитьHexСтрокуИзДвоичныхДанных(Двоичные));
КонецФункции
// Функция восстановления исходной строки из Hex
Функция ХексВСтроку(ХексСтрока)
Двоичные = ПолучитьДвоичныеДанныеИзHexСтроки(ХексСтрока);
Возврат ПолучитьСтрокуИзДвоичныхДанных(Двоичные, КодировкаТекста.UTF8);
КонецФункции
В регистре сведений создаем:
КлючHex (Строка, длина в 2–4 раза больше исходной, без пробелов).КлючОригинал (Строка исходной длины) — для удобного отображения в интерфейсе и отчетах.Значение — полезная нагрузка записи.Запись данных будет выглядеть следующим образом:
МЗ = РегистрыСведений.НашРегистр.СоздатьМенеджерЗаписи();
МЗ.КлючHex = СтрокаВХекс("ААа");
МЗ.КлючОригинал = "ААа";
МЗ.Значение = "4567";
МЗ.Записать(Истина);
Если исходная строка может быть длинной, а измерение требуется сделать компактным и фиксированной длины, применим вычисление хэш-суммы (например, MD5 или SHA-256).
Посмотрим на реализацию вычисления хэша с помощью встроенного объекта ХешированиеДанных:
Функция ПолучитьХешСтроки(ИсходнаяСтрока)
Двоичные = ПолучитьДвоичныеДанныеИзСтроки(ИсходнаяСтрока, КодировкаТекста.UTF8, Ложь);
Хешер = Новый ХешированиеДанных(ХешФункция.MD5);
Хешер.Добавить(Двоичные);
Возврат ВРег(ПолучитьHexСтрокуИзДвоичныхДанных(Хешер.ХешСумма));
КонецФункции
Структура регистра в этом случае включает:
ХешКлюча (Строка фиксированной длины 32 символа для MD5).ИсходныйКлюч (Строка произвольной длины).Значение.При поиске нужной записи в запросе мы накладываем условие на поле ХешКлюча, передавая хэш искомой строки в качестве параметра, что гарантирует точное попадание с учетом регистра.
Если требуется сохранить читаемость ключа непосредственно в измерении без применения хэшей и бинарных преобразований, применим пользовательское экранирование. Договоримся, что перед каждой строчной буквой будем ставить специальный символ (например, косую черту / или знак подчеркивания), а саму строку переводить в верхний регистр.
Разберем алгоритм такого преобразования:
Функция ЗакодироватьКлючСЭкранированием(ИсходнаяСтрока)
Результат = "";
Для Позиция = 1 По СтрДлина(ИсходнаяСтрока) Цикл
Символ = Сред(ИсходнаяСтрока, Позиция, 1);
Если Символ = "/" Тогда
Результат = Результат + "//";
ИначеЕсли Символ = НРег(Символ) И Символ <> ВРег(Символ) Тогда
// Это строчная буква
Результат = Результат + "/" + ВРег(Символ);
Иначе
// Заглавная буква, цифра или спецсимвол
Результат = Результат + Символ;
КонецЕсли;
КонецЦикла;
Возврат Результат;
КонецФункции
При таком подходе строка "ААА" превратится в "ААА", строка "ААа" — в "АА/А", а "ааа" — в "/А/А/А". Все полученные строки состоят только из заглавных букв и экранирующих символов, поэтому регистронезависимая база данных воспримет их как совершенно разные ключи.
Выделим несколько решений, которые часто предлагают на форумах, но которые являются ошибочными или опасными:
Case-Sensitive (например, Russian_100_CS_AS) категорически не поддерживается платформой 1С. Это приведет к ошибкам при реструктуризации таблиц, сбоям обновления конфигурации и нарушению работы стандартных механизмов платформы.ВРег(): Стандартное кодирование Base64 использует как заглавные, так и строчные буквы (где 'a' и 'A' кодируют разные 6-битные значения). Если применить к результату Base64 функцию ВРег(), произойдет потеря данных и коллизия ключей. Вместо Base64 при необходимости текстового алфавита следует использовать Base32 (RFC 4648), состоящий исключительно из символов A-Z и 2-7.Option Compare: Данная конструкция относится к языку Visual Basic / VBA. Во встроенном языке 1С:Предприятия подобных директив управления сравнением не существует.Для создания регистрозависимого регистра сведений в 1С оптимально использовать Hex-кодирование (Способ 1) или MD5-хэширование (Способ 2). Это гарантирует 100% совместимость со всеми поддерживаемыми СУБД (MS SQL Server, PostgreSQL, Oracle, IBM DB2), исключает ошибки дублирования записей и обеспечивает высокую скорость поиска данных через стандартные запросы 1С.