Как найти дублирующиеся строки с учетом регистра в запросе 1С

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

Проблема поиска дублей с учетом регистра (когда "Текст" и "текст" должны считаться разными значениями) является классической для платформы 1С. По умолчанию 1С:Предприятие при работе с клиент-серверными базами (MS SQL Server, PostgreSQL) использует настройки сопоставления (Collation), которые игнорируют регистр. В результате стандартная группировка в запросе или поиск через ВНУТРЕННЕЕ СОЕДИНЕНИЕ объединяет такие строки в одну группу. Рассмотрим подробнее, почему это происходит и какими способами мы можем решить эту задачу.

Почему запрос игнорирует регистр символов?

Прежде чем переходить к коду, выясним причину такого поведения. Когда мы пишем запрос в 1С, платформа транслирует его на язык SQL. В большинстве инсталляций базы данных создаются с параметром _CI_AS (Case Insensitive), что означает "нечувствительность к регистру". Поэтому на уровне СУБД сравнение Строка1 = Строка2 возвращает Истина для значений, различающихся только регистром букв. Чтобы обойти это ограничение, нам придется либо предварительно подготовить данные, либо выполнить дообработку результата программно.

Решение 1: Гибридный метод (Запрос + Таблица значений)

Этот метод считается наиболее оптимальным по соотношению сложности и производительности. Его суть заключается в том, что сначала мы находим "потенциальные" дубли запросом (без учета регистра), а затем фильтруем их в ТаблицеЗначений, используя встроенные механизмы 1С, которые чувствительны к регистру. Проанализируем пример кода:


// 1. Подготовка исходных данных
ТЧСтроки = Новый ТаблицаЗначений;
ТЧСтроки.Колонки.Добавить("Строка", Новый ОписаниеТипов("Строка", Новый КвалификаторыСтроки(100)));
ТЧСтроки.Колонки.Добавить("НомерСтроки", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(10, 0)));

// Добавим данные, где есть дубли по смыслу, но разные по регистру
НоваяСтр = ТЧСтроки.Добавить(); НоваяСтр.Строка = "Значение"; НоваяСтр.НомерСтроки = 1;
НоваяСтр = ТЧСтроки.Добавить(); НоваяСтр.Строка = "значение"; НоваяСтр.НомерСтроки = 2;
НоваяСтр = ТЧСтроки.Добавить(); НоваяСтр.Строка = "Значение"; НоваяСтр.НомерСтроки = 3;

// 2. Выполняем запрос для поиска потенциальных дублей
Запрос = Новый Запрос;
Запрос.УстановитьПараметр("ВТ_ТЧСтроки", ТЧСтроки);
Запрос.Текст = 
"ВЫБРАТЬ
|	вт.Строка КАК Строка,
|	вт.НомерСтроки КАК НомерСтроки
|ПОМЕСТИТЬ ВТ_Данные
|ИЗ
|	&ВТ_ТЧСтроки КАК вт
|;
|
|ВЫБРАТЬ
|	ВТ_Данные.Строка КАК Строка,
|	ВТ_Данные.НомерСтроки КАК НомерСтроки
|ИЗ
|	ВТ_Данные КАК ВТ_Данные
|ГДЕ
|	ВТ_Данные.Строка В (
|		ВЫБРАТЬ вт_вн.Строка 
|		ИЗ ВТ_Данные КАК вт_вн 
|		СГРУППИРОВАТЬ ПО вт_вн.Строка 
|		ИМЕЮЩИЕ КОЛИЧЕСТВО(вт_вн.Строка) > 1
|	)
|УПОРЯДОЧИТЬ ПО Строка";

ТЗДублей = Запрос.Выполнить().Выгрузить();

// 3. Программная дофильтрация с учетом регистра
// Методы НайтиСтроки() и поиск в Структуре в 1С учитывают регистр
Индекс = 0;
Пока Индекс < ТЗДублей.Количество() Цикл
    ТекущаяСтрока = ТЗДублей[Индекс];
    Отбор = Новый Структура("Строка", ТекущаяСтрока.Строка);
    МассивНайденных = ТЗДублей.НайтиСтроки(Отбор);
    
    // Если найдена только одна строка с таким точным регистром - это не дубль
    Если МассивНайденных.Количество() < 2 Тогда
        ТЗДублей.Удалить(ТекущаяСтрока);
    Иначе
        Индекс = Индекс + 1;
    КонецЕсли;
КонецЦикла;

Важный нюанс: метод НайтиСтроки() у объекта ТаблицаЗначений работает с учетом регистра. Таким образом, мы отсекаем те записи, которые SQL посчитал одинаковыми, но 1С видит как разные.

Решение 2: Предварительная трансформация данных (Base64 или Хеширование)

Если данных слишком много для итеративной обработки в цикле, разберем подход с использованием "суррогатных" колонок. Мы можем преобразовать строку в формат, где регистр зафиксирован уникальными символами, перед передачей таблицы в запрос.

  1. Добавим в исходную таблицу колонку КлючРегистра.
  2. Заполним её значением функции Base64Строка(ДвоичныеДанныеСтроки) или рассчитаем ХешированиеДанных (MD5/SHA).
  3. В запросе будем группировать именно по этой колонке.

Посмотрим на пример подготовки данных:


ТЧСтроки.Колонки.Добавить("КлючРегистра", Новый ОписаниеТипов("Строка", Новый КвалификаторыСтроки(255)));

Для Каждого Стр Из ТЧСтроки Цикл
    // Используем Base64 для точного соответствия
    Двоичные = ПолучитьДвоичныеДанныеИзСтроки(Стр.Строка);
    Стр.КлючРегистра = Base64Строка(Двоичные);
КонецЦикла;

Теперь в запросе достаточно использовать СГРУППИРОВАТЬ ПО КлючРегистра. Поскольку Base64-коды для "А" и "а" абсолютно разные, СУБД корректно разделит их на разные группы.

Решение 3: Использование оператора ПОДОБНО (LIKE) в запросе

Существует экспертный способ заставить SQL учитывать регистр без изменения настроек всей базы. Оператор ПОДОБНО в некоторых СУБД (при использовании спецсимволов и экранирования) ведет себя иначе, чем простое равенство. Однако в 1С этот метод крайне сложен в реализации для произвольных строк, так как требует посимвольного сравнения или использования СПЕЦСИМВОЛ.

Рассмотрим ситуацию: если мы точно знаем длину строки, мы можем проверить каждый символ. Но это решение является скорее теоретическим и крайне медленным для реальных задач. Проанализируем почему: для каждого символа придется писать условие ПОДОБНО с проверкой на вхождение в диапазон символов нужного регистра (например, через [А-Я]), что сильно перегружает план выполнения запроса.

Использование функций НРЕГ и ВРЕГ в современных версиях платформы

Начиная с версии 8.3.20, в языке запросов появились функции НРЕГ (перевод в нижний регистр) и ВРЕГ (перевод в верхний регистр). Хотя они сами по себе не делают запрос регистрозависимым, они помогают в обратной задаче — нормализации данных прямо в памяти SQL.

Если ваша цель — наоборот, гарантированно найти все дубли, игнорируя опечатки в регистре, используйте следующий подход:


ВЫБРАТЬ
    НРЕГ(вт.Строка) КАК СтрокаНижнийРегистр,
    КОЛИЧЕСТВО(*) КАК КоличествоПовторов
ИЗ
    ВТ_Данные КАК вт
СГРУППИРОВАТЬ ПО
    НРЕГ(вт.Строка)
ИМЕЮЩИЕ
    КОЛИЧЕСТВО(*) > 1

Подведем итоги

Для эффективного решения задачи поиска дублей с учетом регистра в 1С рекомендуем придерживаться следующих правил:

← На главную