Почему прямой запрос к текстовому реквизиту 1С 7.7 (DBF) возвращает пустой результат?

Программист 1С v7.7 IT и автоматизация бизнеса
← На главную

Описание проблемы

Рассмотрим весьма специфичную и неочевидную проблему, с которой можно столкнуться при работе с прямыми запросами к базам данных 1С:Предприятие 7.7 в формате DBF. Допустим, мы пишем простой запрос для поиска в справочнике ЮрЛица (например, в конфигурации "Торговля и Склад") по части ИНН.

Проанализируем базовый текст запроса, который мы пытаемся выполнить через провайдер VFPOLEDB:


ТекстЗапроса = "
|SELECT
|    ЮрЛица.ID as [Элемент $Справочник.ЮрЛица]
|FROM
|    $Справочник.ЮрЛица as ЮрЛица
|WHERE $ЮрЛица.ИНН LIKE '%" + СокрЛП(СтрокаПоиска) + "%'";

Парадокс заключается в том, что в справочнике гарантированно есть искомый элемент, и обычным запросом 1С по четкому поиску результат находится успешно. Однако прямой запрос возвращает пустой результат. При этом, если мы запрашиваем другие поля (например, Наименование или Код), то они читаются корректно, а вот поле ИНН оказывается абсолютно пустым или содержит "мусор". Выясним причину такого странного поведения.

Поиск причины: сдвиг полей при чтении через OLE DB

Если мы посмотрим на структуру данных через системные файлы 1С (например, 1Cv7.DD), мы можем обнаружить следующее: реквизит ИНН (допустим, поле sp494) хранится в таблице SC***.DBF корректно. Но проблема кроется не в самом ИНН, а в соседних реквизитах.

В ходе детального разбора структуры таблицы выясняется, что в справочнике присутствует другой текстовый реквизит (например, "Полное наименование"), длина которого установлена в 900 символов. Именно здесь кроется архитектурное ограничение.

Проблема возникает из-за ограничения формата DBF (в частности dBASE / FoxPro). Согласно спецификациям этого формата, максимальная длина обычного строкового поля (тип Character) жестко ограничена 254 символами.

Провайдер VFPOLEDB (Visual FoxPro OLE DB Provider), который традиционно используется для прямых запросов к DBF-версии 1С 7.7, строго следует этому стандарту. Когда драйвер встречает поле длиной больше 254 символов, его алгоритм расчета смещения байтов ломается. В результате все колонки, которые идут в таблице после этого "аномального" поля, считываются со сдвигом: драйвер берет куски данных из соседних колонок. Из-за этого наша колонка с ИНН при чтении запросом оказывается пустой или нерелевантной.

Почему штатный движок 1С и SQLite работают нормально?

У вас может возникнуть логичный вопрос: почему сама 1С и внешние компоненты вроде 1sqlite читают базу без ошибок?

  1. Движок 1С 7.7: В качестве драйвера баз данных 1С 7.7 использует встроенный движок CodeBase, который расширяет стандарты классического DBF. Он позволяет создавать строковые поля фиксированной длины вплоть до 999 символов. Поэтому сама платформа прекрасно видит это поле.
  2. 1sqlite (SQLite для 1С): Класс для работы с SQLite (на базе 1С++) использует собственный парсер DBF-файлов, написанный специально под структуру 1С. Этот парсер "осведомлен" об отклонениях 1С и поддержке полей до 999 символов, поэтому смещение колонок вычисляется правильно, и данные не искажаются.

Варианты решения проблемы

Рассмотрим несколько способов устранения сдвига колонок при использовании VFPOLEDB.

  1. Изменение длины реквизита до 254 символов: Самый простой и быстрый способ. Если специфика данных позволяет, уменьшите длину проблемного текстового реквизита в Конфигураторе до 254 символов (лимит формата FoxPro). После реструктуризации базы прямой запрос сразу начнет возвращать правильные данные.
  2. Использование строки неограниченной длины: Если для реквизита (например, сложного наименования) действительно нужно более 254 символов, правильным архитектурным решением в 1С 7.7 будет сделать этот реквизит строкой неограниченной длины. Физически в таблице справочника (SC***.DBF) будет храниться лишь небольшой идентификатор, а сам текст 1С перенесет в отдельный служебный файл 1SBLOB.DBF. Это полностью устранит "сдвиг" полей для любых внешних OLE DB провайдеров.
  3. Смена порядка реквизитов (костыль): Если оставить длину 900, но в конфигураторе сдвинуть этот реквизит в самый низ списка (чтобы он физически создавался в таблице последним), сдвиг не затронет идущие перед ним поля, такие как ИНН.

Бонус: Как найти контрагента по ИНН (без КПП) штатными методами

Посмотрим на практический пример поиска без использования прямых запросов. Если в базе хранится совмещенный реквизит "ИНН\КПП", поиск обычным методом НайтиПоРеквизиту может не дать результата при вводе только ИНН. Разберем функцию для классического перебора справочника с отсечением КПП:


Функция НайтиКонтрагентаПоИННбезКПП(ИННКПП) Экспорт
    ИНН = ВыделитьИНН(ИННКПП); // Ваша функция выделения ИНН из строки
    Спр = СоздатьОбъект("Справочник.ЮрЛица");
    ТЗ  = СоздатьОбъект("ТаблицаЗначений");
    
    ТЗ.НоваяКолонка("КодКонтрагента");
    ТЗ.НоваяКолонка("ЮЛ", "Справочник.ЮрЛица");
    ТЗ.НоваяКолонка("Контрагент", "Справочник.Контрагенты");
    
    Спр.ВыбратьЭлементы(0);
    Пока Спр.ПолучитьЭлемент() = 1 Цикл
        Если Спр.ЭтоГруппа() = 1 Тогда
            Продолжить;
        КонецЕсли;
        
        Если СокрЛП(Спр.ИНН) = "" Тогда
            Продолжить;
        КонецЕсли;
        
        ТекИНН = ВыделитьИНН(СокрЛП(Спр.ИНН));
        
        Если ТекИНН = ИНН Тогда
            ТЗ.НоваяСтрока();
            ТЗ.КодКонтрагента = СокрЛП(Спр.Владелец.Код);
            ТЗ.ЮЛ             = Спр.ТекущийЭлемент();
            ТЗ.Контрагент     = Спр.Владелец;
        КонецЕсли;
    КонецЦикла;
    
    Если ТЗ.КоличествоСтрок() = 0 Тогда
        Возврат 0; // Ничего не нашли
    КонецЕсли;
    
    ТЗ.Сортировать("КодКонтрагента");
    Возврат ТЗ.ПолучитьЗначение(1, 3); // Возвращаем первого найденного
КонецФункции

Такой подход может быть полезен, если база небольшая, и вы не хотите внедрять внешние компоненты или связываться с особенностями OLE DB. Однако при миллионных объемах данных использование прямых запросов (к SQLite или SQL-версии) с грамотной архитектурой таблиц (без полей > 254) останется наиболее оптимальным решением.

← На главную