Как реализовать поиск по подстроке в DBF-файлах в 1С?

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

Работа с внешними источниками данных, такими как файлы формата DBF, часто сопровождается трудностями при реализации гибкого поиска. Стандартные механизмы платформы 1С, использующие объект XBase, отлично справляются с поиском по полному совпадению или по началу строки с помощью индексов, но поиск вхождения подстроки в середину значения требует иного подхода. В этой статье мы подробно разберем, почему стандартный поиск может выдавать неверные результаты, и изучим несколько эффективных способов решения этой задачи.

Анализ проблемы: Почему НайтиПоКлючу работает некорректно?

Рассмотрим ситуацию, с которой столкнулся автор темы. При попытке найти строку "Срань" в колонке "Name", используя оператор сравнения ">=", система возвращает записи типа "Стакан...". Это происходит потому, что поиск по индексу в формате xBase — это, прежде всего, математическое сравнение последовательности байтов. Буква "т" в слове "Стакан" следует в алфавитном порядке позже буквы "р" в искомом слове "Срань".

Использование оператора ">=" заставляет систему найти первую запись, которая либо в точности совпадает с искомой, либо является следующей по алфавиту (лексикографически большей). Таким образом, мы получаем не поиск вхождения подстроки, а позиционирование на ближайшую подходящую по порядку запись. Для поиска именно подстроки (вхождения части текста в любое место колонки) нам необходимо использовать другие методы.

Способ 1. Прямой перебор (Full Scan)

Если объем DBF-файла относительно невелик (до нескольких десятков тысяч записей), самым простым и надежным способом будет прямой перебор всех записей в цикле. В этом случае нам не нужно тратить ресурсы на создание временных индексных файлов .cdx, что само по себе является медленной операцией ввода-вывода.

Проанализируем пример кода, который реализует такой поиск:


Таблица = Новый XBase;
Таблица.ОткрытьФайл(ПутьКФайлу, , Истина); // Открываем только на чтение

Если Таблица.Открыта() Тогда
    Пока Таблица.Следующая() Цикл
        // Проверяем наличие подстроки без учета регистра
        Если Найти(ВРег(Таблица.Name), ВРег(СтрокаПоиска)) > 0 Тогда
            Сообщить("Найдено совпадение в строке: " + Таблица.Name);
            // Здесь можно добавить найденную запись в массив или таблицу значений
        КонецЕсли;
    КонецЦикла;
    Таблица.ЗакрытьФайл();
КонецЕсли;

В данном примере мы используем функцию ВРег() для приведения обеих строк к верхнему регистру. Это критически важно, так как поиск в DBF по умолчанию чувствителен к регистру, и "срань" не будет найдена в строке "СРАНЬ ГОСПОДНЯ".

Способ 2. Использование технологии ADO и SQL-запросов

Для более крупных файлов или в случаях, когда требуется высокая скорость поиска без загрузки всех данных в память 1С, оптимальным решением станет использование Microsoft ActiveX Data Objects (ADO). Это позволит нам обращаться к DBF-файлу как к реляционной базе данных и использовать мощный синтаксис SQL, включая оператор LIKE.

Разберем по шагам, как это реализовать:

  1. Создадим объект ADODB.Connection.
  2. Настроим строку подключения (Provider), указав путь к каталогу с DBF-файлами.
  3. Выполним SQL-запрос с использованием конструкции LIKE '%СтрокаПоиска%'.

Connection = Новый COMОбъект("ADODB.Connection");
// Для Windows 7/10 обычно используется провайдер Microsoft.Jet.OLEDB.4.0
// Путь указывается только к ПАПКЕ, где лежат файлы
ПутьККаталогу = "C:\DBF_Data\";
СтрокаСоединения = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + ПутьККаталогу + ";Extended Properties=dBASE IV;";

Попытка
    Connection.Open(СтрокаСоединения);
    
    // Имя файла в запросе используется как имя таблицы
    ТекстЗапроса = "SELECT * FROM [Vozv0297.dbf] WHERE [Name] LIKE '%" + СтрокаПоиска + "%'";
    Recordset = Connection.Execute(ТекстЗапроса);
    
    Пока НЕ Recordset.EOF Цикл
        Сообщить("Найдено через ADO: " + Recordset.Fields("Name").Value);
        Recordset.MoveNext();
    КонецЦикла;
    
    Connection.Close();
Исключение
    Сообщить("Ошибка подключения: " + ОписаниеОшибки());
КонецПопытки;

Важный момент: Символ % в SQL означает любое количество любых символов. Окружая искомую строку этими знаками, мы гарантируем поиск вхождения в любом месте поля.

Способ 3. Загрузка в Таблицу Значений

Если вам необходимо выполнять несколько различных поисков или фильтраций по одним и тем же данным, бывает эффективнее один раз прочитать весь DBF-файл в объект ТаблицаЗначений. После этого поиск можно выполнять либо методом НайтиСтроки(), либо (что еще профессиональнее) загрузить эту таблицу в запрос через МенеджерВременныхТаблиц.

Рассмотрим вариант с НайтиСтроки():


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

// ... код заполнения ТЗ из DBF через объект XBase ...

// Поиск всех строк, где содержится подстрока
// К сожалению, НайтиСтроки ищет только полное совпадение. 
// Поэтому для подстроки используем перебор ТЗ или ПостроительЗапроса
Для Каждого Стр Из ТЗ_Данные Цикл
    Если Найти(Стр.Name, СтрокаПоиска) > 0 Тогда
        // Обработка результата
    КонецЕсли;
КонецЦикла;

Важные технические нюансы работы с DBF

При работе с файлами DBF в 1С необходимо учитывать несколько неочевидных факторов, которые могут влиять на результат поиска:

  1. Кодировка данных: DBF-файлы могут быть созданы в кодировке Windows (ANSI) или DOS (OEM-866). Если в объекте XBase свойство Кодировка установлено неверно, вместо кириллицы вы получите "кракозябры", и поиск Найти() никогда не вернет истину.
  2. Удаленные записи: В формате DBF записи не удаляются физически сразу, а лишь помечаются специальным флагом. Чтобы не находить "мусорные" данные, проверяйте свойство Таблица.ЗаписьУдалена() в цикле перебора или установите Таблица.ОтображатьУдаленные = Ложь.
  3. Длина полей: Поля в DBF имеют фиксированную длину. Если длина поля 50 символов, а записано "Срань", то остаток строки будет заполнен пробелами. Рекомендуется использовать функцию СокрЛП() при получении значения из колонки.

Выясним причину, по которой создание индексов "на лету" (как делал автор вопроса) часто не имеет смысла. Создание .cdx индекса оправдано только в двух случаях: если вы ищете по полному совпадению или по началу строки в огромном массиве данных (миллионы записей). Для поиска подстроки "внутри" текста стандартные b-tree индексы DBF бесполезны, так как они строятся от первого символа строки.

Таким образом, для решения задачи автора наиболее подходящим методом будет либо простой перебор XBase с использованием Найти() и ВРег(), либо использование ADO для выполнения SQL-запроса LIKE.

← На главную