При разработке прямых запросов к базе данных 1С:Предприятие 7.7 с использованием замечательной внешней компоненты 1sqlite (SQLiteBase) разработчики часто сталкиваются с проблемой несовместимости форматов хранения ссылок на объекты. Типичный сценарий: необходимо объединить таблицу справочника, содержащую реквизит типа «Документ» (неопределенного вида), и таблицу конкретного документа. Прямое сравнение полей через оператор JOIN не дает результатов, так как форматы идентификаторов в этих таблицах различаются.
В рамках данной статьи мы подробно разберем, почему возникает эта проблема, рассмотрим несколько способов приведения данных к единому формату и проанализируем, как эти решения влияют на производительность запросов и использование индексов СУБД.
Прежде чем перейти к написанию кода, давайте разберем физическую структуру хранения ссылок в СУБД 1С 7.7 (будь то DBF или SQL-версия). Формат записи идентификатора документа напрямую зависит от типизации реквизита в конфигураторе:
Документ.СчетФактураЛК), то в базе данных сохраняется только «чистый» уникальный идентификатор документа. Его длина составляет 9 символов (например, " 1LS "). Этот формат полностью соответствует полю IDDOC в физической таблице самого документа.IDDocDef, например, " 56D"), а последующие 9 символов — это сам IDDOC (например, " 1LS "). Получается склеенная строка вида " 56D 1LS ".Когда мы пытаемся выполнить связь таблиц по условию ON Бланки.Документ = ДокСф.IDDoc, СУБД пытается сравнить строку из 13 символов со строкой из 9 символов. Разумеется, такое сравнение всегда возвращает ложь (False), и мы получаем пустой результат запроса.
Рассмотрим два основных способа решения этой проблемы с помощью средств SQLite.
Самый простой и очевидный путь — привести 9-символьный идентификатор документа к 13-символьному формату прямо в условии связи. Для этого нам нужно приклеить слева к полю IDDOC таблицы документов 4-символьный маркер вида документа.
В SQLite для конкатенации (склеивания) строк используется специальный оператор двойного пайпа ||. Обратите внимание, что привычный для многих языков программирования и других диалектов SQL оператор плюс + в SQLite предназначен исключительно для арифметического сложения (при его использовании строки будут неявно приведены к числам, что даст неверный результат).
Разберем по шагам практический пример запроса с использованием конкатенации:
текстЗапроса = "
|SELECT
| Бланки.DESCR,
| Бланки.Документ [ДокСпр :Документ],
| ДокСф.IDDOC [ДокДок :Документ.СчетФактураЛК]
|FROM Бланки
|INNER JOIN ДокСф
| ON :ВидДокумента.СчетФактураЛК || ДокСф.IDDoc = Бланки.Документ
|WHERE ДокСф.БланкНалоговой = :ПустойИД;
|";
В данном запросе конструкция :ВидДокумента.СчетФактураЛК — это типизированный типизатор метаданных 1С, который компонента 1sqlite автоматически преобразует в реальный 4-символьный идентификатор вида документа (в нашем случае ' 56D'). К нему присоединяется 9-символьное значение IDDOC, в результате чего получается полноценный 13-символьный идентификатор, который успешно сопоставляется с полем Бланки.Документ.
Хотя первый метод прост в реализации, он имеет существенный недостаток с точки зрения производительности на больших базах данных. Когда мы оборачиваем индексируемое поле таблицы (в данном случае ДокСф.IDDoc) в любые операции или функции (включая конкатенацию ||), оптимизатор SQLite лишается возможности использовать индекс по этому полю. СУБД будет вынуждена выполнять полный перебор (Full Table Scan) таблицы документов для каждого сопоставления.
Чтобы избежать падения производительности, мы можем пойти обратным путем: не увеличивать поле документа, а наоборот — «отрезать» лишние 4 символа вида документа от 13-символьного поля справочника. Для этого в SQLite применяется стандартная функция substr.
Давайте проанализируем этот подход подробнее. Мы знаем, что IDDOC начинается с 5-го символа в 13-символьной строке. Таким образом, функция substr(Бланки.Документ, 5) вернет нам чистый 9-символьный идентификатор.
Перепишем наше условие связи:
текстЗапроса = "
|SELECT
| Бланки.DESCR,
| Бланки.Документ [ДокСпр :Документ],
| ДокСф.IDDOC [ДокДок :Документ.СчетФактураЛК]
|FROM Бланки
|INNER JOIN ДокСф
| ON ДокСф.IDDoc = substr(Бланки.Документ, 5)
|WHERE ДокСф.БланкНалоговой = :ПустойИД;
|";
Почему этот вариант значительно быстрее? В данном случае левая часть выражения ДокСф.IDDoc остается чистой, без применения функций. СУБД SQLite вычислит значение substr(...) для текущей записи справочника всего один раз, а затем мгновенно найдет соответствующий документ в таблице ДокСф по индексу первичного ключа (индексы по полям IDDOC всегда создаются системой автоматически).
При работе со строками и идентификаторами в 1С 7.7 через SQLite полезно знать еще несколько важных механизмов, которые помогут сделать ваши запросы быстрыми и надежными:
trim, используйте кастомное правило сравнения COLLATE _1C, поставляемое компонентой. Пример: ON ДокСф.IDDoc COLLATE _1C = substr(Бланки.Документ, 5) COLLATE _1C.1sqlite нет необходимости вручную прописывать вызовы create virtual table ... using dbeng(...). Вы можете сразу обращаться к виртуальным таблицам метаданных напрямую, используя стандартные префиксы, например: Справочник_БланкиСчетфактур или Документ_СчетФактураЛК. Компонента самостоятельно распознает их и подключит нужные таблицы «на лету».Подводя итог, для быстрого и правильного связывания таблиц в 1С 7.7 через прямые SQL-запросы всегда оценивайте объемы данных. Если таблицы содержат десятки тысяч записей, отдавайте предпочтение функции substr для сохранения работоспособности индексов.