При написании прямых SQL-запросов к базе данных 1С:Предприятия 7.7 с использованием внешней компоненты 1С++ и класса ODBCRecordSet часто возникает задача передать список значений или объектов во временную таблицу для последующей фильтрации. Разработчики нередко сталкиваются с ситуацией, когда метод УложитьСписокОбъектов13 отрабатывает без явных ошибок, но результирующий SQL-запрос возвращает пустой набор данных или условия сравнения вида IN (SELECT VAL FROM #Таблица) всегда вычисляются как ложные.
Разберем детально, почему это происходит, в чем заключается разница форматов хранения ссылок в 1С 7.7 и как правильно организовать передачу списков объектов с сохранением иерархии и типизации.
Чтобы понять, почему условие фильтрации дает сбой, проанализируем внутреннее устройство хранения ссылочных данных в MS SQL Server для платформы 1С 7.7:
ID и PARENTID в таблицах справочников, идентификатор хранится в виде строки фиксированной длины CHAR(9).CHAR(13). Первые 4 символа содержат код вида объекта метаданных в системе счисления Base36, а оставшиеся 9 символов — уникальный идентификатор самого объекта.val char(13) и заполняет ее полными 13-символьными идентификаторами. Если в запросе сравнивать Товары.PARENTID (тип CHAR(9)) со значением из #SpisRod (тип CHAR(13)), сравнение строк разной длины никогда не будет истинным.УложитьСписокОбъектов, метод УложитьСписокОбъектов13 не имеет встроенного механизма рекурсивного разворота иерархии групп справочника. В таблицу попадают строго те элементы, которые были непосредственно переданы во входном списке.Если цель заключается в фильтрации по конкретному справочнику или по полю PARENTID, нет необходимости использовать 13-значные идентификаторы. Рассмотрим штатный метод УложитьСписокОбъектов, который создает поле val char(9) и умеет автоматически разворачивать иерархию папок:
// Создаем объект Recordset
Запрос = СоздатьОбъект("ODBCRecordSet");
// Третий параметр "1" включает автоматический разворот иерархии переданных групп
Запрос.УложитьСписокОбъектов(СписокГруппИЭлементов, "#SpisRod", 1);
// Выполняем SQL-запрос с фильтром по 9-значному идентификатору
ТекстЗапроса = "
|SELECT
| Спр.ID as [Элемент $Справочник.Номенклатура],
| Спр.DESCR as [Наименование $Строка]
|FROM
| $Справочник.Номенклатура as Спр
|WHERE
| Спр.PARENTID IN (SELECT val FROM #SpisRod)
|";
ТЗ = Запрос.ВыполнитьИнструкцию(ТекстЗапроса);
Если требуется получить список 13-значных идентификаторов с учетом иерархии (например, для последующего сравнения с реквизитом неопределенного вида в журнале документов), разберем комбинированный подход: укладываем объекты с иерархией через УложитьСписокОбъектов, а затем преобразуем их во временную таблицу формата CHAR(13) средствами SQL и метапарсера 1С++.
Запрос = СоздатьОбъект("ODBCRecordSet");
// 1. Укладываем 9-символьные ID с разворачиванием иерархии
Запрос.УложитьСписокОбъектов(СписокЭлементов, "#Temp9", 1);
// 2. Формируем 13-значную таблицу с префиксом вида метаданных
ТекстПодготовки = "
|SELECT
| $ВидСправочника36.Номенклатура + val AS val
|INTO #Spis13
|FROM #Temp9;
|";
Запрос.ВыполнитьИнструкцию(ТекстПодготовки);
// 3. Используем полученную таблицу для фильтрации неопределенного реквизита
ТекстЗапроса = "
|SELECT
| Жур.IDDOC as [Док $Документ]
|FROM
| _1SJOURN as Жур
|WHERE
| Жур.$ОбщийРеквизит.ТекущийЭлемент IN (SELECT val FROM #Spis13)
|";
ТЗ = Запрос.ВыполнитьИнструкцию(ТекстЗапроса);
В ситуациях, когда требуется полностью контролировать типы данных, структуру создаваемой временной таблицы и передавать произвольные наборы колонок (включая принудительное приведение к CHAR(9) или CHAR(13)), посмотрим на программную реализацию универсальной выгрузки:
Процедура УложитьТаблицу_л(пТЗ, ИмяТаб, Колонки="")
Перем ТЗ, Запрос, рс_;
Запрос = СоздатьОбъект("ODBCRecordSet");
Если ПустаяСтрока(Колонки) = 1 Тогда
типВхода = ТипЗначенияСтр(пТЗ);
Попытка
Если типВхода = "ТаблицаЗначений" Тогда
ТЗ = пТЗ;
ИначеЕсли типВхода = "СписокЗначений" Тогда
ТЗ = СоздатьОбъект("ТаблицаЗначений");
пТЗ.Выгрузить(ТЗ);
КонецЕсли;
Исключение
Возврат;
КонецПопытки;
Иначе
Попытка
пТЗ.Выгрузить(ТЗ,,, Колонки);
Исключение
ТЗ = СоздатьОбъект("ТаблицаЗначений");
пТЗ.Выгрузить(ТЗ);
КонецПопытки;
КонецЕсли;
СтрCreate = "create table %Имя% (/*column_definition*/)";
СтрInsert = "insert into %Имя% values(/*?*/)";
Тип = ""; Длина = ""; Точность = ""; ФорматнаяСтрока = "";
КолонкиЕсть = 0;
Для Тек = 1 По ТЗ.КоличествоКолонок() Цикл
Идентификатор = ТЗ.ПолучитьПараметрыКолонки(Тек, Тип, Длина, Точность,,, ФорматнаяСтрока);
Если (ПустаяСтрока(Колонки) = 0) И (Найти(Колонки, Идентификатор) = 0) Тогда
Продолжить;
КонецЕсли;
КолонкиЕсть = 1;
Если ПустаяСтрока(ФорматнаяСтрока) = 0 Тогда
SQL_тип = ФорматнаяСтрока;
Запрос.ДобПараметр(1,);
ИначеЕсли ПустаяСтрока(Тип) = 1 Тогда
SQL_тип = "char(9)";
Запрос.ДобПараметр(1, 14, 9, 0);
ИначеЕсли Тип = "Число" Тогда
Если Длина = 0 Тогда Длина = 38; Точность = 10; КонецЕсли;
SQL_тип = "numeric(" + Длина + "," + Точность + ")";
Запрос.ДобПараметр(1, 11, Длина, Точность);
ИначеЕсли Тип = "Строка" Тогда
Если Длина = 0 Тогда Длина = 255; КонецЕсли;
SQL_тип = "varchar(" + Длина + ")";
Запрос.ДобПараметр(1, 15, Длина, 0);
ИначеЕсли Тип = "Дата" Тогда
SQL_тип = "datetime";
Запрос.ДобПараметр(1, 8, 0, 0);
ИначеЕсли Найти(Тип, ".") > 0 Тогда
SQL_тип = "char(9)";
Запрос.ДобПараметр(1, 14, 9, 0);
Иначе
SQL_тип = "char(13)";
Запрос.ДобПараметр(1, 14, 13, 0);
ТЗ.УстановитьПараметрыКолонки(Идентификатор,,,, "?2");
КонецЕсли;
СтрCreate = СтрЗаменить(СтрCreate, "/*,*/", ", ");
Если Тек = ТЗ.КоличествоКолонок() Тогда
СтрCreate = СтрЗаменить(СтрCreate, "/*column_definition*/", Идентификатор + " " + SQL_тип + "/*,*/");
Иначе
СтрCreate = СтрЗаменить(СтрCreate, "/*column_definition*/", Идентификатор + " " + SQL_тип + "/*,*//*column_definition*/");
КонецЕсли;
СтрInsert = СтрЗаменить(СтрInsert, "/*,*/", ", ");
СтрInsert = СтрЗаменить(СтрInsert, "/*?*/", "?/*,*//*?*/");
КонецЦикла;
Если КолонкиЕсть = 1 Тогда
рс_ = СоздатьОбъект("ODBCRecordset");
ЗапросУдаления = "set nocount on if exists (select * from tempdb..sysobjects where id=object_id('tempdb..%Имя%') and sysstat & 0xf = 3 ) drop table %Имя%;";
рс_.ВыполнитьИнструкцию(СтрЗаменить(ЗапросУдаления, "%Имя%", ИмяТаб));
ЗапросСоздания = СтрCreate + " set nocount off";
рс_.ВыполнитьИнструкцию(СтрЗаменить(ЗапросСоздания, "%Имя%", ИмяТаб));
Запрос.Подготовить(СтрЗаменить(СтрInsert, "%Имя%", ИмяТаб));
Запрос.ВыполнитьSQL_ИзТЗ(ТЗ, 100);
КонецЕсли;
КонецПроцедуры
Для обеспечения максимального быстродействия при работе с временными таблицами в MS SQL Server обратим внимание на следующие правила:
Запрос.ВыполнитьИнструкцию("CREATE CLUSTERED INDEX IX_TMP_VAL ON #SpisRod (VAL);");
CHAR(9)), а 13-символьный (CHAR(13)) оставляйте исключительно для граф журналов и реквизитов неопределенного вида.RIGHT(VAL, 9).