В процессе сопровождения и администрирования баз данных на платформе 1С:Предприятие 7.7 часто возникает задача поиска дубликатов в справочниках. Особую сложность представляют случаи, когда коды нужно сравнивать не полностью, а лишь по определенному количеству символов (например, когда значащей частью кода являются только первые 8 знаков, а остальное — системный мусор или префиксы). В этой статье мы подробно разберем, почему встроенный язык запросов 1С 7.7 не справляется с этой задачей напрямую, и рассмотрим три надежных и быстрых способа решения проблемы.
Разработчики, привыкшие к возможностям платформы 1С v8.х, часто пытаются решить эту задачу стандартным встроенным запросом. Однако встроенный язык запросов 1С 7.7 (так называемый «черный запрос») имеет жесткие ограничения. В нем нельзя использовать строковые функции, такие как Лев(), СокрЛП() или Формат(), непосредственно внутри условий группировки или выборки. Попытка написать конструкцию вроде Группировка Лев(Код, 8) приведет к синтаксической ошибке на этапе компиляции модуля.
Поэтому нам приходится использовать альтернативные пути: работу с коллекциями в оперативной памяти, прямые запросы к СУБД (для SQL-версий) или виртуальные базы данных в ОЗУ.
Если ваша база данных имеет средний размер (до нескольких десятков тысяч элементов в справочнике) или если конфигурация не должна зависеть от сторонних внешних компонент (ВК), то идеальным решением будет выгрузка кодов в ТаблицуЗначений с последующей сортировкой и поиском дубликатов.
Этот способ работает в десятки раз быстрее обычного перебора методом НайтиЗначение(), так как сортировка больших массивов данных в оперативной памяти 1С 7.7 оптимизирована на уровне платформы.
Разберем по шагам алгоритм реализации:
ТаблицаЗначений и добавляем в него две колонки: для хранения ссылки на элемент справочника и для усеченного до 8 символов кода.Лев() и предварительно очищаем от пробелов функцией СокрЛП().Посмотрим на практический пример кода:
Процедура НайтиДубликатыШтатнымиСредствами()
// Создаем таблицу значений для анализа данных в ОЗУ
ТЗ = СоздатьОбъект("ТаблицаЗначений");
ТЗ.НоваяКолонка("Элемент");
ТЗ.НоваяКолонка("Код8");
// Выбираем элементы справочника
Спр = СоздатьОбъект("Справочник.Номенклатура");
Спр.ВыбратьЭлементы();
Пока Спр.ПолучитьЭлемент() = 1 Цикл
// Исключаем группы, если дубликаты ищем только среди элементов
Если Спр.ЭтоГруппа() = 1 Тогда
Продолжить;
КонецЕсли;
ТЗ.НоваяСтрока();
ТЗ.Элемент = Спр.ТекущийЭлемент();
// Обрезаем код до 8 символов, предварительно убрав пробелы
ТЗ.Код8 = Лев(СокрЛП(Спр.Код), 8);
КонецЦикла;
// Быстро сортируем таблицу по усеченному коду
ТЗ.Сортировать("Код8");
// Ищем дубликаты путем последовательного сравнения соседних строк
КоличествоСтрок = ТЗ.КоличествоСтрок();
Для НомерСтроки = 2 По КоличествоСтрок Цикл
ТекущийКод = ТЗ.ПолучитьЗначение(НомерСтроки, "Код8");
ПредыдущийКод = ТЗ.ПолучитьЗначение(НомерСтроки - 1, "Код8");
Если ТекущийКод = ПредыдущийКод Тогда
Сообщить("Обнаружен дубликат кода: " + ТекущийКод);
Сообщить(" - Элемент 1: " + ТЗ.ПолучитьЗначение(НомерСтроки - 1, "Элемент"));
Сообщить(" - Элемент 2: " + ТЗ.ПолучитьЗначение(НомерСтроки, "Элемент"));
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Если вы работаете на SQL-версии платформы 1С 7.7, самым производительным решением будет выполнение прямого запроса к базе данных Microsoft SQL Server. Это позволит выполнить всю тяжелую работу по группировке и поиску совпадений непосредственно на стороне СУБД, вернув в 1С готовый результат за доли секунды.
Простые конструкции запросов с использованием подзапросов через условие WHERE LEFT(code, 8) IN (SELECT...) могут сильно нагружать сервер БД и приводить к избыточному сканированию таблиц (Table Scan). Чтобы избежать падения производительности, мы применим оптимизированное внутреннее соединение INNER JOIN с предварительно сгруппированным подзапросом.
Обратите внимание, что для поиска именно дубликатов (совпадений от двух и более раз) в секции фильтрации агрегатных функций HAVING мы должны использовать условие строго больше единицы: COUNT(*) > 1.
Пример SQL-запроса для поиска дубликатов:
SELECT
main_table.id as [Элемент $Справочник.Номенклатура],
main_table.code as [ПолныйКод],
sub_table.code8 as [УрезанныйКод]
FROM
$Справочник.Номенклатура as main_table
INNER JOIN (
-- Шаг 1: Группируем элементы по первым 8 символам кода и ищем повторения
SELECT
LEFT(code, 8) as code8
FROM
$Справочник.Номенклатура
GROUP BY
LEFT(code, 8)
HAVING
COUNT(*) > 1
) as sub_table ON LEFT(main_table.code, 8) = sub_table.code8
ORDER BY
sub_table.code8
Использование этого запроса через внешнюю компоненту 1С++ позволяет моментально получить объект типа ИндексированнаяТаблица с готовым списком дублирующихся позиций номенклатуры даже на базах объемом в миллионы строк.
Что делать, если база данных файловая (DBF), но объектов в ней слишком много для комфортной обработки в оперативной памяти средствами встроенного языка 1С? В этом случае отличным компромиссом является использование встроенного SQL-движка SQLite, предоставляемого компонентой 1С++.
Мы можем выгрузить данные во временную базу данных SQLite прямо в оперативной памяти (режим :memory:) и выполнить SQL-запрос к ней локально на клиентском компьютере.
При составлении запроса для SQLite важно помнить о синтаксических нюансах:
LEFT(). Вместо нее для обрезки строки следует использовать стандартную функцию substr(code, 1, 8).GROUP BY ... HAVING count(*) > 1, так как аналитические оконные функции типа OVER(PARTITION BY...) поддерживаются только в новых версиях ядра SQLite и могут вызвать ошибку синтаксиса на старых версиях DLL-библиотек 1С++.Проанализируем пример программной реализации этого метода:
Процедура НайтиДубликатыЧерезSQLite()
ТекстЗапроса = "
|SELECT
| id [Номенклатура $Справочник.Номенклатура],
| code as ПолныйКод,
| substr(code, 1, 8) as Код8
|FROM
| Справочник_Номенклатура
|WHERE
| substr(code, 1, 8) IN (
| SELECT substr(code, 1, 8)
| FROM Справочник_Номенклатура
| GROUP BY substr(code, 1, 8)
| HAVING count(*) > 1
| )
|ORDER BY
| Код8
|";
// Инициализируем локальную базу данных SQLite в ОЗУ
База = СоздатьОбъект("SQLiteBase");
База.Открыть(":memory:");
Запрос = База.НовыйЗапрос();
итДанные = СоздатьОбъект("ИндексированнаяТаблица");
// Выполняем локальный SQL-запрос
Запрос.ВыполнитьЗапрос(ТекстЗапроса, итДанные);
// Выводим результаты в отчет
итДанные.ВыбратьСтроки();
Пока итДанные.ПолучитьСтроку() = 1 Цикл
Сообщить("Урезанный код: " + итДанные.Код8 +
" | Элемент: " + итДанные.Номенклатура +
" | Полный код базы: " + итДанные.ПолныйКод);
КонецЦикла;
КонецПроцедуры
Мы детально рассмотрели три инструмента поиска дубликатов кодов в 1С 7.7. Для быстрого разового анализа небольших справочников идеально подойдет Способ 1 (Таблица Значений) — он не требует установки внешних компонент и прост в отладке. Для крупных промышленных баз данных на MS SQL Server безальтернативным лидером по скорости работы является Способ 2 (прямой SQL-запрос). Если же перед вами стоит задача оптимизировать работу на файловой базе данных, связка из 1С++ и SQLite (Способ 3) обеспечит максимальную производительность без необходимости перехода на клиент-серверную архитектуру.