В процессе разработки на платформе 1С:Предприятие часто возникает задача, когда необходимо сопоставить данные из двух разных таблиц значений. Стандартными методами коллекции ТаблицаЗначений выполнить полноценное соединение (особенно левое или правое) без обхода в цикле невозможно. Использование циклов для этой задачи крайне неэффективно с точки зрения производительности и изящности кода. Оптимальным решением является использование механизма запросов.
Однако напрямую передать две таблицы значений в запрос и сразу соединить их через оператор ЛЕВОЕ СОЕДИНЕНИЕ платформа 1С не позволяет. Чтобы решить эту задачу, нам потребуется использовать временные таблицы и соблюдать несколько строгих правил платформы. Разберем этот процесс подробно и по шагам.
Это самый важный нюанс, из-за которого у большинства начинающих разработчиков не получается передать таблицу значений в запрос. Если ваши таблицы значений создаются программно в коде, их колонки по умолчанию имеют тип Произвольный. Запрос 1С работает непосредственно с СУБД, которая требует четкого понимания типов данных. Если передать нетипизированную таблицу значений в запрос, платформа вернет ошибку «Тип не может быть выбран в запросе».
Давайте посмотрим на пример правильного создания и типизации колонок для двух таблиц значений. Для этого мы будем использовать объект ОписаниеТипов.
// Создаем Описания типов для наших колонок
ОписаниеТипаСтрока = Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(100));
ОписаниеТипаЧисло = Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2));
ОписаниеТипаАдрес = Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(250));
// Создаем первую таблицу значений (Таблица1)
Таблица1 = Новый ТаблицаЗначений;
Таблица1.Колонки.Добавить("Имя", ОписаниеТипаСтрока);
Таблица1.Колонки.Добавить("Сумма", ОписаниеТипаЧисло);
// Заполняем Таблицу1 тестовыми данными
СтрокаТ1 = Таблица1.Добавить();
СтрокаТ1.Имя = "Иванов";
СтрокаТ1.Сумма = 15000;
СтрокаТ2 = Таблица1.Добавить();
СтрокаТ2.Имя = "Петров";
СтрокаТ2.Сумма = 23000;
// Создаем вторую таблицу значений (Таблица2)
Таблица2 = Новый ТаблицаЗначений;
Таблица2.Колонки.Добавить("Имя", ОписаниеТипаСтрока);
Таблица2.Колонки.Добавить("Адрес", ОписаниеТипаАдрес);
// Заполняем Таблицу2 тестовыми данными
СтрокаТ3 = Таблица2.Добавить();
СтрокаТ3.Имя = "Иванов";
СтрокаТ3.Адрес = "Москва, ул. Ленина, д. 5";
Особое внимание обратите на то, что типы связующих колонок (в нашем случае это колонка Имя) в обеих таблицах должны совпадать с точностью до длины и допустимых символов. Разные типы данных в соединении приведут к некорректной работе запроса или ошибкам СУБД.
Для того чтобы временные таблицы, созданные в рамках одного запроса, могли «видеть» друг друга и участвовать в последующих соединениях, нам обязательно нужно использовать специальный объект платформы — МенеджерВременныхТаблиц.
Если мы попытаемся выполнить пакетный запрос без привязки менеджера, платформа выдаст ошибку времени выполнения. Инициализация менеджера связывает контекст выполнения запроса с оперативной памятью, где временно размещаются переданные таблицы значений.
Запрос = Новый Запрос;
// Создаем и связываем менеджер временных таблиц
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
Теперь перейдем к написанию самого запроса. Логика решения состоит из трех последовательных шагов в рамках одного пакета запросов:
Таблица1 во временную таблицу ВТ_Таблица1 и обязательно индексируем ее по ключевому полю связи.Таблица2 во временную таблицу ВТ_Таблица2 и также индексируем по полю связи.Имя.Важный совет по оптимизации: Индексирование временных таблиц с помощью конструкции ИНДЕКСИРОВАТЬ ПО критически важно, если ваши таблицы значений содержат более пары сотен строк. Без индексов СУБД будет выполнять полное сканирование таблиц (Full Scan) при каждом соединении строк, что приведет к сильному падению производительности информационной базы.
Рассмотрим полный текст запроса и его программное выполнение:
Запрос.Текст =
"ВЫБРАТЬ
| Таблица1.Имя КАК Имя,
| Таблица1.Сумма КАК Сумма
|ПОМЕСТИТЬ ВТ_Таблица1
|ИЗ
| &Таблица1 КАК Таблица1
|ИНДЕКСИРОВАТЬ ПО
| Имя
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| Таблица2.Имя КАК Имя,
| Таблица2.Адрес КАК Адрес
|ПОМЕСТИТЬ ВТ_Таблица2
|ИЗ
| &Таблица2 КАК Таблица2
|ИНДЕКСИРОВАТЬ ПО
| Имя
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| Т1.Имя КАК Имя,
| Т1.Сумма КАК Сумма,
| ЕСТЬNULL(Т2.Адрес, ""Адрес не указан"") КАК Адрес
|ИЗ
| ВТ_Таблица1 КАК Т1
| ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Таблица2 КАК Т2
| ПО Т1.Имя = Т2.Имя";
// Передаем наши типизированные таблицы значений в параметры запроса
Запрос.УстановитьПараметр("Таблица1", Таблица1);
Запрос.УстановитьПараметр("Таблица2", Таблица2);
// Выполняем запрос и выгружаем результат в новую таблицу значений
РезультатСоединения = Запрос.Выполнить().Выгрузить();
В результате выполнения этого кода мы получим новую таблицу значений РезультатСоединения, в которой будут сопоставлены данные по имени сотрудников. Обратите внимание на использование функции ЕСТЬNULL в запросе — она позволяет корректно обработать ситуации, когда во второй таблице не нашлось сопоставленной записи по ключевому полю.
При использовании данного метода обязательно нужно учитывать внутренние архитектурные ограничения платформы 1С:Предприятие:
СправочникОбъект или ДокументОбъект вместо их ссылок).Применяя данные правила на практике, вы сможете быстро и безопасно выполнять операции соединения табличных данных любой сложности непосредственно в оперативной памяти сервера 1С.