В практике разработчика на платформе 1С:Предприятие 7.7 (в народе называемой «клюшками») часто встречается ситуация, когда идеально написанный и отлаженный на DBF-версии запрос внезапно перестает работать после перехода на SQL. Одной из самых коварных проблем является фильтрация по счетам в так называемых «черных запросах». Проанализируем ситуацию, когда условие по счету в тексте запроса приводит к пустой выборке, хотя данные в базе точно есть.
Рассмотрим классический пример. У нас есть документ ПоступлениеТМЦ, в шапке которого присутствует реквизит СчетКредит. Мы пишем стандартный встроенный запрос, чтобы собрать данные по конкретному счету, например, 60.01. Мы инициализируем переменную через СчетПоКоду("60.01") и добавляем условие в запрос. Однако на выходе получаем пустоту. Самое странное начинается при попытке инвертировать условие: если написать Условие (СчетДок <> Сч60), запрос начинает выдавать вообще все записи, включая те, где счет равен 60.01. Это явный признак того, что штатный транслятор запросов 1С некорректно обрабатывает типы данных на стороне SQL-сервера.
Для того чтобы понять, почему это происходит, разберем, как 1С 7.7 хранит ссылки на объекты в SQL. В отличие от простых строк или чисел, ссылки на объекты (справочники, документы, счета) хранятся в специальном формате. Если реквизит в конфигураторе имеет строго определенный тип, например, Справочник.Контрагенты, то в таблице SQL создается поле фиксированной длины. Но если тип реквизита задан как неопределенный — <<Счет>> (без указания конкретного плана счетов), ситуация усложняется.
Проанализируем структуру: для неопределенных типов 1С создает составное поле, которое должно хранить не только идентификатор объекта, но и идентификатор его метаданных (какой именно это план счетов). При выполнении запроса встроенный движок 1С пытается перевести (транслировать) ваше условие СчетДок = Сч60 в SQL-команду WHERE. Из-за ошибки в логике этого транслятора, сравнение происходит некорректно. Система может пытаться сравнить внутренний идентификатор в формате Base36 (те самые странные символы вроде 2U. 3, которые видны в отладчике) с объектом, который передан как параметр, и не находит совпадения.
Самый быстрый и эффективный способ заставить запрос работать — это заставить 1С выполнять фильтрацию не на стороне сервера SQL, а на стороне клиента. Для этого в объекте Запрос существует специальный метод. Разберем его применение на примере кода:
Запрос = СоздатьОбъект("Запрос");
// Отключаем выполнение условий на стороне SQL-сервера
Запрос.ВключитьSql(0);
ТекстЗапроса = "
|Период с ДатаНачала по ДатаКонца;
|СчетДок = Документ.ПоступлениеТМЦ.СчетКредит;
|Условие (СчетДок = Сч60);
|// ... остальные группировки и функции
|";
Если Запрос.Выполнить(ТекстЗапроса) = 0 Тогда
Возврат;
КонецЕсли;
Важный нюанс: метод ВключитьSql(0) имеет серьезный побочный эффект. Проанализируем последствия: при его использовании 1С выгружает все данные из таблиц документов за указанный период во временные таблицы на локальном компьютере, и только потом применяет фильтры. Если в вашей базе сотни тысяч документов, такой отчет будет строиться очень долго и создаст огромную нагрузку на сеть. Используйте этот метод только в тех случаях, когда выборка документов за период невелика.
Если в вашей конфигурации используется только один план счетов (например, «Основной»), то использование неопределенного типа <<Счет>> технически избыточно. Посмотрим на этот вопрос с точки зрения архитектуры базы данных. Когда вы меняете тип реквизита в конфигураторе с общего <<Счет>> на конкретный Счет.Основной, вы упрощаете структуру таблиц SQL. Поле в базе данных становится «понятным» для транслятора запросов, и ошибка исчезает сама собой.
Рассмотрим шаги для этого решения:
СчетКредит.Счет выберите конкретный план счетов из списка.Для профессиональной разработки на 1С 7.7 «черные запросы» считаются устаревшим и медленным инструментом. Проанализируем ситуацию: штатный запрос ограничен возможностями платформы 20-летней давности. Если вам нужна настоящая скорость и надежность на SQL, следует использовать внешние компоненты, такие как 1C++.
Прямой запрос позволяет написать условие на чистом языке T-SQL, где вы сами контролируете, как сравниваются идентификаторы счетов. Рассмотрим, как это выглядит концептуально:
// Пример логики прямого запроса через SQL
ТекстЗапроса = "
|SELECT
| Doc.IDDoc as [Док $Документ.ПоступлениеТМЦ],
| $Doc.СчетКредит as [Счет $Счет.Основной]
|FROM
| $Документ.ПоступлениеТМЦ as Doc
|WHERE
| $Doc.СчетКредит = :НужныйСчет
|";
// Здесь фильтрация происходит мгновенно на сервере
Такой подход полностью исключает ошибки трансляции, так как вы обходите встроенный движок 1С. Однако это требует навыков написания SQL-запросов и понимания структуры таблиц DH* и DT*.
Выясним, какой путь выбрать в зависимости от обстоятельств. Если вам нужно «починить быстро» и данных немного — используйте Запрос.ВключитьSql(0). Если вы сопровождаете собственную конфигурацию и хотите избавиться от проблемы навсесьда — измените тип реквизита на конкретный план счетов в метаданных. Если же вы стремитесь к максимальной производительности системы — начинайте осваивать прямые запросы к SQL-серверу.
Помните, что 1С 7.7 на SQL работает через слой эмуляции, который не всегда идеален. Столкновение с внутренними кодами объектов (Base36) в отладчике — это верный признак того, что система потеряла типизацию объекта, и ваша задача как программиста — либо вернуть эту типизацию через метаданные, либо подсказать системе, как правильно обрабатывать данные программно.