В практике разработки на платформе 1С:Предприятие 7.7 при использовании MS SQL Server программисты часто сталкиваются со странным поведением стандартных запросов. Ситуация выглядит следующим образом: когда мы передаем в условие запроса СписокЗначений, содержащий один документ, запрос отрабатывает корректно. Однако стоит добавить в этот список второй документ, как выборка становится пустой или возвращает неверные данные. Проанализируем, почему это происходит, и рассмотрим эффективные способы решения этой проблемы.
Чтобы понять суть проблемы, нам необходимо заглянуть «под капот» взаимодействия 1С 7.7 и SQL Server. В базе данных SQL идентификатор документа хранится в поле IDDOC, которое имеет тип Char(9). Это уникальный 9-символьный код документа внутри таблицы.
Однако в самой платформе 1С 7.7 существует понятие «длинного идентификатора», который включает в себя 13 символов: 4 символа идентификатора типа документа и 9 символов самого IDDOC. Когда в СписокЗначений передается несколько элементов, стандартный движок запросов 1С пытается оптимизировать работу через создание временной таблицы в SQL (обычно с префиксом #RGTEMP).
Проблема заключается в том, что при заполнении этой временной таблицы платформа записывает туда полные 13-значные типизированные идентификаторы (например, ' 745 2CYME '), а затем пытается выполнить JOIN этой таблицы с таблицей регистра по полю IDDOC, длина которого всего 9 символов (например, ' 2CYME '). Из-за несовпадения строк (лишние 4 символа типа в начале) условие соединения никогда не выполняется, и запрос возвращает пустой результат. Выясним, как мы можем обойти это ограничение платформенного движка.
Самый простой, но «тяжелый» способ заставить запрос работать — это отключить трансляцию запроса в SQL-код. Для этого используется метод ВключитьSQL(0). Рассмотрим пример реализации:
// Создание объекта типа Запрос
Запрос = СоздатьОбъект("Запрос");
Запрос.ВключитьSQL(0); // Отключаем выполнение на сервере SQL
ТекстЗапроса =
"//{{ЗАПРОС(Сформировать)
|Период с ВыбНачПериода по ВыбКонПериода;
|ТекущийДокумент = Регистр.Продажи.ТекущийДокумент;
|Номенклатура = Регистр.Продажи.Номенклатура;
|Функция СуммаПрод = Сумма(Регистр.Продажи.Сумма);
|Группировка Номенклатура;
|Условие(ТекущийДокумент в СписокФильтр);
|"//}}ЗАПРОС
;
Важный нюанс: при использовании ВключитьSQL(0) 1С выгружает все необходимые данные из SQL-таблиц во временные файлы формата DBF на локальной машине пользователя и обрабатывает их там. Если регистр содержит сотни тысяч записей, скорость выполнения упадет в десятки раз (с нескольких секунд до нескольких минут). Поэтому используйте этот метод только для небольших выборок или в тех случаях, когда производительность не критична.
Проанализируем более элегантный способ, который позволяет сохранить высокую скорость работы и избежать ошибок типизации. Вместо использования стандартного оператора в, мы можем использовать встроенный метод объекта СписокЗначений непосредственно в условии запроса. Посмотрим, как изменить текст запроса:
// Вместо Условие(ТекущийДокумент в СписокФильтр);
// Используем следующий синтаксис:
ТекстЗапроса = "
|...
|Условие(СписокФильтр.Принадлежит(ТекущийДокумент) = 1);
|...
|";
Когда движок 1С видит в условии метод Принадлежит(), он часто меняет стратегию трансляции. Вместо создания временной таблицы и JOIN, он может перечислить идентификаторы документов через оператор IN ('ID1', 'ID2'...) непосредственно в тексте WHERE. Это позволяет SQL-серверу корректно сравнивать 9-символьные значения и использовать индексы для быстрого поиска.
Аналогично предыдущему методу, мы можем использовать функцию поиска по списку. Это также заставляет 1С обрабатывать условие иначе, чем при стандартном операторе в. Рассмотрим пример:
// Использование метода НайтиЗначение для фильтрации
ТекстЗапроса = "
|...
|Условие(СписокФильтр.НайтиЗначение(ТекущийДокумент) <> 0);
|...
|";
Как показывает практика, этот метод работает стабильно и быстро. Он избавляет нас от необходимости типизировать документы в списке вручную, так как механизм НайтиЗначение внутри запроса корректно сопоставляет идентификаторы.
Разберем еще несколько моментов, которые помогут избежать подобных проблем в будущем:
T-SQL, где вы полностью контролируете передачу параметров и типизацию полей IDDOC.СписокФильтр. Если список пустой, 1С может проигнорировать условие и выдать данные по всем документам, что приведет к зависанию системы.Таким образом, мы выяснили, что основной причиной сбоя в работе запроса 1С 7.7 на SQL является некорректная трансляция списка значений во временную таблицу. Используя методы Принадлежит() или НайтиЗначение() вместо оператора в, мы «обманываем» транслятор и получаем корректный результат с сохранением производительности.