Как сделать измерение регистра сведений чувствительным к регистру букв в 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При проектировании регистров сведений разработчики нередко сталкиваются с задачей сохранения строковых кодов, идентификаторов или артикулов, где регистр символов имеет значение: например, "ААА", "ААа" и "АаА" должны быть тремя совершенно разными записями. Однако стандартная попытка записать такие строки в строковое измерение независимого регистра сведений приводит к ошибке дублирования ключа. Разберем подробно, почему платформа 1С ведет себя именно так и какими техническими способами можно реализовать кейсозависимое хранение данных.

Причина поведения: почему СУБД считает строки одинаковыми

Проанализируем архитектуру хранения данных в платформе 1С:Предприятие. Измерения регистра сведений формируют первичный ключ (уникальный индекс) таблицы СУБД. При создании базы данных платформа жестко задает правила сортировки и сопоставления символов (Collation) с признаком CI (Case-Insensitive), например, Cyrillic_General_CI_AS в Microsoft SQL Server или регистронезависимые правила сортировки в PostgreSQL.

Из-за этого правила на уровне базы данных действуют следующие ограничения:

  1. Конфликт уникальности: Для СУБД строки "ААА", "ААа" и "ааа" являются абсолютно эквивалентными. При попытке вставить вторую запись с тем же набором символов в другом регистре СУБД возвращает ошибку нарушения уникальности первичного ключа.
  2. Поведение в запросах: Любые операции сравнения (=, ПОДОБНО, В, условия соединения ПО) на языке запросов 1С транслируются в SQL-запрос и выполняются на стороне СУБД без учета регистра букв.

Посмотрим на показательный пример в консоли запросов:


ВЫБРАТЬ
    "Совпало" КАК Результат
ГДЕ
    "ААА" = "ААа"

Такой запрос вернет строку "Совпало", поскольку для СУБД обе строковые константы идентичны.

Разница в обработке строк: память vs база данных

Важно различать, где именно выполняется код. В отличие от языка запросов, встроенный язык 1С при работе с объектами в оперативной памяти выполняет сравнение строк с учетом регистра.

Рассмотрим пример работы с коллекциями в памяти:


ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Ключ", Новый ОписаниеТипов("Строка"));
ТЗ.Колонки.Добавить("Значение", Новый ОписаниеТипов("Строка"));

Стр = ТЗ.Добавить();
Стр.Ключ = "ААА";
Стр.Значение = "123";

Стр = ТЗ.Добавить();
Стр.Ключ = "ААа";
Стр.Значение = "4567";

// Выполняем поиск по строке в нижнем регистре
ПоискСтрок = ТЗ.НайтиСтроки(Новый Структура("Ключ", "ааа"));
КоличествоНайденных = ПоискСтрок.Количество(); // Результат будет 0

Метод НайтиСтроки(), оператор = во встроенном языке, а также поиск по ключу в объекте Соответствие чувствительны к регистру. Однако, как только данные передаются в базу данных через объект РегистрСведенийНаборЗаписей или РегистрСведенийМенеджерЗаписи, начинает действовать логика СУБД.

Способ 1. Шестнадцатеричное кодирование (Hex / Base16)

Разберем самый надежный и прозрачный способ: хранить в измерении регистра шестнадцатеричное представление двоичных данных исходной строки. Поскольку символы разного регистра имеют разные байтовые коды в кодировке UTF-8 (например, 'A' — это 0x41, а 'a' — 0x61), результирующая строка будет состоять исключительно из цифр и заглавных латинских букв 0-9, A-F. Такое значение будет уникальным даже в регистронезависимой СУБД.

Реализуем функции прямого и обратного преобразования:


// Функция кодирования строки в Hex-представление
Функция СтрокаВХекс(ИсходнаяСтрока)
    Двоичные = ПолучитьДвоичныеДанныеИзСтроки(ИсходнаяСтрока, КодировкаТекста.UTF8, Ложь);
    Возврат ВРег(ПолучитьHexСтрокуИзДвоичныхДанных(Двоичные));
КонецФункции

// Функция восстановления исходной строки из Hex
Функция ХексВСтроку(ХексСтрока)
    Двоичные = ПолучитьДвоичныеДанныеИзHexСтроки(ХексСтрока);
    Возврат ПолучитьСтрокуИзДвоичныхДанных(Двоичные, КодировкаТекста.UTF8);
КонецФункции

В регистре сведений создаем:

  1. Измерение КлючHex (Строка, длина в 2–4 раза больше исходной, без пробелов).
  2. Реквизит КлючОригинал (Строка исходной длины) — для удобного отображения в интерфейсе и отчетах.
  3. Реквизит Значение — полезная нагрузка записи.

Запись данных будет выглядеть следующим образом:


МЗ = РегистрыСведений.НашРегистр.СоздатьМенеджерЗаписи();
МЗ.КлючHex = СтрокаВХекс("ААа");
МЗ.КлючОригинал = "ААа";
МЗ.Значение = "4567";
МЗ.Записать(Истина);

Способ 2. Хэширование с учетом регистра

Если исходная строка может быть длинной, а измерение требуется сделать компактным и фиксированной длины, применим вычисление хэш-суммы (например, MD5 или SHA-256).

Посмотрим на реализацию вычисления хэша с помощью встроенного объекта ХешированиеДанных:


Функция ПолучитьХешСтроки(ИсходнаяСтрока)
    Двоичные = ПолучитьДвоичныеДанныеИзСтроки(ИсходнаяСтрока, КодировкаТекста.UTF8, Ложь);
    Хешер = Новый ХешированиеДанных(ХешФункция.MD5);
    Хешер.Добавить(Двоичные);
    Возврат ВРег(ПолучитьHexСтрокуИзДвоичныхДанных(Хешер.ХешСумма));
КонецФункции

Структура регистра в этом случае включает:

  1. Измерение ХешКлюча (Строка фиксированной длины 32 символа для MD5).
  2. Реквизит ИсходныйКлюч (Строка произвольной длины).
  3. Реквизит Значение.

При поиске нужной записи в запросе мы накладываем условие на поле ХешКлюча, передавая хэш искомой строки в качестве параметра, что гарантирует точное попадание с учетом регистра.

Способ 3. Экранирование строчных символов

Если требуется сохранить читаемость ключа непосредственно в измерении без применения хэшей и бинарных преобразований, применим пользовательское экранирование. Договоримся, что перед каждой строчной буквой будем ставить специальный символ (например, косую черту / или знак подчеркивания), а саму строку переводить в верхний регистр.

Разберем алгоритм такого преобразования:


Функция ЗакодироватьКлючСЭкранированием(ИсходнаяСтрока)
    Результат = "";
    Для Позиция = 1 По СтрДлина(ИсходнаяСтрока) Цикл
        Символ = Сред(ИсходнаяСтрока, Позиция, 1);
        Если Символ = "/" Тогда
            Результат = Результат + "//";
        ИначеЕсли Символ = НРег(Символ) И Символ <> ВРег(Символ) Тогда
            // Это строчная буква
            Результат = Результат + "/" + ВРег(Символ);
        Иначе
            // Заглавная буква, цифра или спецсимвол
            Результат = Результат + Символ;
        КонецЕсли;
    КонецЦикла;
    Возврат Результат;
КонецФункции

При таком подходе строка "ААА" превратится в "ААА", строка "ААа" — в "АА/А", а "ааа" — в "/А/А/А". Все полученные строки состоят только из заглавных букв и экранирующих символов, поэтому регистронезависимая база данных воспримет их как совершенно разные ключи.

Ошибочные подходы, которых следует избегать

Выделим несколько решений, которые часто предлагают на форумах, но которые являются ошибочными или опасными:

  1. Смена параметров сортировки (Collation) базы данных в СУБД: Изменение параметров сортировки на Case-Sensitive (например, Russian_100_CS_AS) категорически не поддерживается платформой 1С. Это приведет к ошибкам при реструктуризации таблиц, сбоям обновления конфигурации и нарушению работы стандартных механизмов платформы.
  2. Использование Base64 с последующим ВРег(): Стандартное кодирование Base64 использует как заглавные, так и строчные буквы (где 'a' и 'A' кодируют разные 6-битные значения). Если применить к результату Base64 функцию ВРег(), произойдет потеря данных и коллизия ключей. Вместо Base64 при необходимости текстового алфавита следует использовать Base32 (RFC 4648), состоящий исключительно из символов A-Z и 2-7.
  3. Использование директивы Option Compare: Данная конструкция относится к языку Visual Basic / VBA. Во встроенном языке 1С:Предприятия подобных директив управления сравнением не существует.

Итоги и рекомендации

Для создания регистрозависимого регистра сведений в 1С оптимально использовать Hex-кодирование (Способ 1) или MD5-хэширование (Способ 2). Это гарантирует 100% совместимость со всеми поддерживаемыми СУБД (MS SQL Server, PostgreSQL, Oracle, IBM DB2), исключает ошибки дублирования записей и обеспечивает высокую скорость поиска данных через стандартные запросы 1С.

← На главную