Проблема поиска дублей с учетом регистра (когда "Текст" и "текст" должны считаться разными значениями) является классической для платформы 1С. По умолчанию 1С:Предприятие при работе с клиент-серверными базами (MS SQL Server, PostgreSQL) использует настройки сопоставления (Collation), которые игнорируют регистр. В результате стандартная группировка в запросе или поиск через ВНУТРЕННЕЕ СОЕДИНЕНИЕ объединяет такие строки в одну группу. Рассмотрим подробнее, почему это происходит и какими способами мы можем решить эту задачу.
Прежде чем переходить к коду, выясним причину такого поведения. Когда мы пишем запрос в 1С, платформа транслирует его на язык SQL. В большинстве инсталляций базы данных создаются с параметром _CI_AS (Case Insensitive), что означает "нечувствительность к регистру". Поэтому на уровне СУБД сравнение Строка1 = Строка2 возвращает Истина для значений, различающихся только регистром букв. Чтобы обойти это ограничение, нам придется либо предварительно подготовить данные, либо выполнить дообработку результата программно.
Этот метод считается наиболее оптимальным по соотношению сложности и производительности. Его суть заключается в том, что сначала мы находим "потенциальные" дубли запросом (без учета регистра), а затем фильтруем их в ТаблицеЗначений, используя встроенные механизмы 1С, которые чувствительны к регистру. Проанализируем пример кода:
// 1. Подготовка исходных данных
ТЧСтроки = Новый ТаблицаЗначений;
ТЧСтроки.Колонки.Добавить("Строка", Новый ОписаниеТипов("Строка", Новый КвалификаторыСтроки(100)));
ТЧСтроки.Колонки.Добавить("НомерСтроки", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(10, 0)));
// Добавим данные, где есть дубли по смыслу, но разные по регистру
НоваяСтр = ТЧСтроки.Добавить(); НоваяСтр.Строка = "Значение"; НоваяСтр.НомерСтроки = 1;
НоваяСтр = ТЧСтроки.Добавить(); НоваяСтр.Строка = "значение"; НоваяСтр.НомерСтроки = 2;
НоваяСтр = ТЧСтроки.Добавить(); НоваяСтр.Строка = "Значение"; НоваяСтр.НомерСтроки = 3;
// 2. Выполняем запрос для поиска потенциальных дублей
Запрос = Новый Запрос;
Запрос.УстановитьПараметр("ВТ_ТЧСтроки", ТЧСтроки);
Запрос.Текст =
"ВЫБРАТЬ
| вт.Строка КАК Строка,
| вт.НомерСтроки КАК НомерСтроки
|ПОМЕСТИТЬ ВТ_Данные
|ИЗ
| &ВТ_ТЧСтроки КАК вт
|;
|
|ВЫБРАТЬ
| ВТ_Данные.Строка КАК Строка,
| ВТ_Данные.НомерСтроки КАК НомерСтроки
|ИЗ
| ВТ_Данные КАК ВТ_Данные
|ГДЕ
| ВТ_Данные.Строка В (
| ВЫБРАТЬ вт_вн.Строка
| ИЗ ВТ_Данные КАК вт_вн
| СГРУППИРОВАТЬ ПО вт_вн.Строка
| ИМЕЮЩИЕ КОЛИЧЕСТВО(вт_вн.Строка) > 1
| )
|УПОРЯДОЧИТЬ ПО Строка";
ТЗДублей = Запрос.Выполнить().Выгрузить();
// 3. Программная дофильтрация с учетом регистра
// Методы НайтиСтроки() и поиск в Структуре в 1С учитывают регистр
Индекс = 0;
Пока Индекс < ТЗДублей.Количество() Цикл
ТекущаяСтрока = ТЗДублей[Индекс];
Отбор = Новый Структура("Строка", ТекущаяСтрока.Строка);
МассивНайденных = ТЗДублей.НайтиСтроки(Отбор);
// Если найдена только одна строка с таким точным регистром - это не дубль
Если МассивНайденных.Количество() < 2 Тогда
ТЗДублей.Удалить(ТекущаяСтрока);
Иначе
Индекс = Индекс + 1;
КонецЕсли;
КонецЦикла;
Важный нюанс: метод НайтиСтроки() у объекта ТаблицаЗначений работает с учетом регистра. Таким образом, мы отсекаем те записи, которые SQL посчитал одинаковыми, но 1С видит как разные.
Если данных слишком много для итеративной обработки в цикле, разберем подход с использованием "суррогатных" колонок. Мы можем преобразовать строку в формат, где регистр зафиксирован уникальными символами, перед передачей таблицы в запрос.
КлючРегистра.Base64Строка(ДвоичныеДанныеСтроки) или рассчитаем ХешированиеДанных (MD5/SHA).Посмотрим на пример подготовки данных:
ТЧСтроки.Колонки.Добавить("КлючРегистра", Новый ОписаниеТипов("Строка", Новый КвалификаторыСтроки(255)));
Для Каждого Стр Из ТЧСтроки Цикл
// Используем Base64 для точного соответствия
Двоичные = ПолучитьДвоичныеДанныеИзСтроки(Стр.Строка);
Стр.КлючРегистра = Base64Строка(Двоичные);
КонецЦикла;
Теперь в запросе достаточно использовать СГРУППИРОВАТЬ ПО КлючРегистра. Поскольку Base64-коды для "А" и "а" абсолютно разные, СУБД корректно разделит их на разные группы.
Существует экспертный способ заставить SQL учитывать регистр без изменения настроек всей базы. Оператор ПОДОБНО в некоторых СУБД (при использовании спецсимволов и экранирования) ведет себя иначе, чем простое равенство. Однако в 1С этот метод крайне сложен в реализации для произвольных строк, так как требует посимвольного сравнения или использования СПЕЦСИМВОЛ.
Рассмотрим ситуацию: если мы точно знаем длину строки, мы можем проверить каждый символ. Но это решение является скорее теоретическим и крайне медленным для реальных задач. Проанализируем почему: для каждого символа придется писать условие ПОДОБНО с проверкой на вхождение в диапазон символов нужного регистра (например, через [А-Я]), что сильно перегружает план выполнения запроса.
Начиная с версии 8.3.20, в языке запросов появились функции НРЕГ (перевод в нижний регистр) и ВРЕГ (перевод в верхний регистр). Хотя они сами по себе не делают запрос регистрозависимым, они помогают в обратной задаче — нормализации данных прямо в памяти SQL.
Если ваша цель — наоборот, гарантированно найти все дубли, игнорируя опечатки в регистре, используйте следующий подход:
ВЫБРАТЬ
НРЕГ(вт.Строка) КАК СтрокаНижнийРегистр,
КОЛИЧЕСТВО(*) КАК КоличествоПовторов
ИЗ
ВТ_Данные КАК вт
СГРУППИРОВАТЬ ПО
НРЕГ(вт.Строка)
ИМЕЮЩИЕ
КОЛИЧЕСТВО(*) > 1
Для эффективного решения задачи поиска дублей с учетом регистра в 1С рекомендуем придерживаться следующих правил:
НайтиСтроки() в ТЗ для финального фильтра).СтрСравнить(), которая возвращает 0 при полном совпадении (включая регистр).