Объединение временных таблиц в запросах 1С — одна из самых востребованных операций при разработке отчетов, обработок и сложных алгоритмов. Даже опытные программисты иногда сталкиваются с нюансами: то LEFT JOIN работает не так, как ожидалось, то UNION ALL дублирует строки, то временная таблица внезапно пустая после объединения. В этой статье разберем 5 проверенных способов объединения временных таблиц в одном запросе, включая редкие приемы, которые экономят время и ресурсы.
Особенность работы с временными таблицами в 1С:Предприятие 8.3 заключается в том, что они существуют только в рамках сеанса выполнения запроса. Это накладывает ограничения на методы объединения, но одновременно открывает возможности для оптимизации. Например, можно избежать лишних обращений к базе данных, если правильно сконструировать запрос с подзапросами или использовать ВЫБРАТЬ РАЗРЕШЕННЫЕ для фильтрации данных на лету.
Мы не будем ограничиваться базовыми примерами из документации. Вместо этого покажем, как объединять таблицы с разной структурой, как избежать потери данных при внешних соединениях, и почему иногда проще использовать объединение в памяти (через коллекции 1С), чем пытаться сделать всё в одном SQL-запросе. В конце статьи — FAQ с ответами на типичные ошибки и чек-лист для проверки корректности объединения.
1. Базовые методы объединения: LEFT JOIN vs INNER JOIN
Начнем с классики: соединение таблиц по ключу. В 1С это реализуется через операторы ЛЕВОЕ СОЕДИНЕНИЕ (LEFT JOIN) и СОЕДИНЕНИЕ (INNER JOIN). Разница между ними критична:
- 🔹 LEFT JOIN — возвращает все строки из левой (первой) таблицы, даже если в правой нет совпадений. Поля из правой таблицы заполняются
NULL. - 🔹 INNER JOIN — возвращает только строки, где есть совпадения в обеих таблицах. Ненайденные данные игнорируются.
- 🔹 FULL JOIN — в 1С отсутствует напрямую, но его можно эмулировать через
UNION(об этом ниже).
Пример запроса с LEFT JOIN для объединения временных таблиц #Таблица1 и #Таблица2 по полю КодКонтрагента:
ВЫБРАТЬ
Таблица1.КодКонтрагента КАК Код,
Таблица1.Наименование КАК НаименованиеЛевой,
Таблица2.СуммаДоговора КАК Сумма
ИЗ
#Таблица1 КАК Таблица1
ЛЕВОЕ СОЕДИНЕНИЕ #Таблица2 КАК Таблица2
ПО Таблица1.КодКонтрагента = Таблица2.КодКонтрагента
⚠️ Внимание: Если в правой таблице (#Таблица2) есть дубликаты по ключу соединения, тоLEFT JOINсоздаст декартово произведение для этих строк. Чтобы избежать размножения данных, предварительно удалите дубликаты или используйте агрегацию (ГРУППИРОВКА ПО).
А теперь — типичная ошибка новичков: использование INNER JOIN там, где нужны все данные из левой таблицы. Например, если вы объединяете таблицу заказов с таблицей оплат, и некоторые заказы ещё не оплачены, то INNER JOIN просто проигнорирует их. В таких случаях всегда проверяйте результат на полноту данных!
2. UNION ALL: вертикальное объединение таблиц
Когда нужно объединить две таблицы по строкам (т.е. сложить их вертикально), используется оператор ОБЪЕДИНИТЬ ВСЕ (UNION ALL). Это актуально, если таблицы имеют одинаковую структуру, но содержат разные наборы данных. Например, объединяем остатки товаров с двух складов:
ВЫБРАТЬ
ОстаткиСклад1.Номенклатура КАК Номенклатура,
ОстаткиСклад1.Количество КАК Количество,
"Склад 1" КАК Склад
ИЗ
#ОстаткиСклад1 КАК ОстаткиСклад1
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
ОстаткиСклад2.Номенклатура,
ОстаткиСклад2.Количество,
"Склад 2" КАК Склад
ИЗ
#ОстаткиСклад2 КАК ОстаткиСклад2
Ключевые моменты:
- 📌 Количество и порядок колонок в обоих запросах должны совпадать.
- 📌
UNION ALLне удаляет дубликаты (в отличие отUNIONбезALL, который работает медленнее). - 📌 Если структуры таблиц различаются, используйте
NULLдля "пустых" колонок.
⚠️ Внимание: При объединении больших таблиц (100 000+ строк) UNION ALL может тормозить. В таких случаях лучше использовать временные таблицы с индексами или разбивать запрос на части.
Если нужно добавить колонку с источником данных (например, "Склад 1"/"Склад 2"), используйте конструкцию "Склад 1" КАК Склад прямо в запросе, как в примере выше.
3. Подзапросы как альтернатива соединениям
Иногда объединение таблиц через JOIN приводит к избыточной выборке или сложным условиям. В таких случаях помогают подзапросы — вложенные запросы, которые выполняются для каждой строки основного запроса. Например, нужно к каждой строке таблицы заказов "приклеить" сумму последней оплаты:
ВЫБРАТЬ
Заказы.Номер КАК НомерЗаказа,
Заказы.Дата,
(
ВЫБРАТЬ ПЕРВЫЕ 1
Оплаты.Сумма
ИЗ
#Оплаты КАК Оплаты
ГДЕ
Оплаты.Заказ = Заказы.Ссылка
УПОРЯДОЧИТЬ ПО
Оплаты.Дата УБЫВ
) КАК СуммаПоследнейОплаты
ИЗ
#Заказы КАК Заказы
Преимущества подзапросов:
- 🔧 Гибкость: можно использовать
ПЕРВЫЕ N, агрегатные функции (СУММА,МАКСИМУМ). - 🔧 Читаемость: логика выборки данных сосредоточена в одном месте.
- 🔧 Производительность: подзапрос выполняется только для нужных строк (в отличие от
JOIN, который может перемножить таблицы).
Однако у подзапросов есть и минусы:
- ⚠️ Медленнее, если подзапрос выполняется для каждой строки основной таблицы (проблема "N+1").
- ⚠️ Не всегда поддерживаются в управляемых формах (зависит от версии платформы).
Когда подзапрос работает быстрее JOIN?
Если в правой таблице мало данных (например, 1 строка на 100 строк левой таблицы), то подзапрос с ГДЕ может быть эффективнее, чем JOIN, который перебирает все комбинации.
4. Объединение таблиц с разной структурой
Частая проблема: временные таблицы имеют разные колонки, но их нужно объединить. Например, в одной таблице есть поле ЦенаЗакупа, а в другой — ЦенаПродажи. Решение — явно указать все колонки в результате, заполняя отсутствующие значения NULL:
ВЫБРАТЬ
Таблица1.Номенклатура КАК Номенклатура,
Таблица1.ЦенаЗакупа КАК ЦенаЗакупа,
NULL КАК ЦенаПродажи
ИЗ
#Таблица1 КАК Таблица1
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
Таблица2.Номенклатура,
NULL КАК ЦенаЗакупа,
Таблица2.ЦенаПродажи
ИЗ
#Таблица2 КАК Таблица2
Альтернативный подход — нормализовать структуру перед объединением, создав промежуточные таблицы с одинаковыми колонками:
// Создаем временную таблицу с унифицированной структурой
ВЫБРАТЬ
Номенклатура,
ЦенаЗакупа КАК Цена,
"Закупка" КАК ТипЦены
ИЗ
#Таблица1
ПОМЕСТИТЬ #НормализованныеЦены
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
Номенклатура,
ЦенаПродажи КАК Цена,
"Продажа" КАК ТипЦены
ИЗ
#Таблица2
| Метод | Когда использовать | Ограничения |
|---|---|---|
UNION ALL с NULL |
Таблицы с небольшими различиями в структуре | Может усложнить чтение кода |
| Промежуточная нормализованная таблица | Сложные различия в структуре, много колонок | Требует дополнительной памяти |
| Объединение в памяти (коллекции 1С) | Малые объемы данных, нужна гибкость | Медленнее для больших массивов |
5. Объединение через коллекции 1С (в памяти)
Если данные невелики (до 10 000 строк), иногда проще объединить таблицы не в запросе, а в коде с помощью коллекций: Массив, Структура, Соответствие или ТаблицаЗначений. Это дает полный контроль над логикой объединения.
Пример: объединяем две таблицы значений по полю Код, добавляя данные из второй таблицы в первую:
// Предполагаем, что Таблица1 и Таблица2 - это ТаблицаЗначений
Для Каждого Строка Из Таблица2 Цикл
НайденнаяСтрока = Таблица1.Найти(Строка.Код, "Код");
Если НайденнаяСтрока = Неопределено Тогда
НоваяСтрока = Таблица1.Добавить();
НоваяСтрока.Код = Строка.Код;
НоваяСтрока.ДанныеИзТаблицы2 = Строка.Значение;
Иначе
НайденнаяСтрока.ДанныеИзТаблицы2 = Строка.Значение;
КонецЕсли;
КонецЦикла;
Плюсы этого метода:
- 🛠️ Полный контроль над логикой (можно добавлять условия, преобразования данных).
- 🛠️ Нет ограничений на структуру таблиц.
- 🛠️ Легко отлаживать (можно поставить точку останова и посмотреть промежуточные данные).
Минусы:
- ❌ Медленнее для больших объемов данных (ОЗУ vs SQL-оптимизации).
- ❌ Требует ручного управления памятью (например, очистки временных коллекций).
⚠️ Внимание: Если вы работаете с управляемыми формами в тонком клиенте, избегайте больших коллекций в памяти — это может привести к подвисаниям интерфейса. В таких случаях лучше использовать SQL-запросы.
☑️ Проверка перед объединением в памяти
6. Оптимизация объединения: индексы и планы выполнения
Даже правильно написанный запрос может тормозить, если не учтены индексы и планы выполнения. Вот что нужно проверить:
- 🔍 Индексы по полям соединения: Если объединяете таблицы по полю
КодКонтрагента, убедитесь, что оно проиндексировано. В временных таблицах индексы создаются автоматически для полей, участвующих вГРУППИРОВКА ПОилиУПОРЯДОЧИТЬ ПО. - 🔍 Порядок таблиц в JOIN: В 1С первая таблица в запросе считается "ведущей". Если она меньше, запрос выполнится быстрее.
- 🔍 Использование ВЫБРАТЬ РАЗРЕШЕННЫЕ: Если временная таблица формируется из виртуальной таблицы (например,
Документ.ЗаказПокупателя), добавьтеВЫБРАТЬ РАЗРЕШЕННЫЕдля фильтрации данных на уровне СУБД.
Пример оптимизированного запроса с явным указанием индексируемого поля:
ВЫБРАТЬ
Заказы.Номер КАК Номер,
Заказы.Дата,
Оплаты.Сумма КАК СуммаОплаты
ИЗ
#Заказы КАК Заказы
ЛЕВОЕ СОЕДИНЕНИЕ #Оплаты КАК Оплаты
ПО Заказы.Ссылка = Оплаты.Заказ
ИНДЕКСИРОВАТЬ ПО Оплаты.Заказ // Явное указание индекса
ГДЕ
Заказы.Дата МЕЖДУ &НачалоПериода И &КонецПериода
Чтобы посмотреть, как 1С выполняет запрос, используйте план выполнения:
ОбъектЗапроса = Новый Запрос;
ОбъектЗапроса.Текст = "ВАШ_ЗАПРОС";
ОбъектЗапроса.УстановитьПараметр("НачалоПериода", НачалоДня(ТекущаяДата()));
ПланВыполнения = ОбъектЗапроса.Выполнить().ПолучитьПланВыполнения();
Если в плане выполнения вы видите TableScan (полное сканирование таблицы), значит, индексы не используются. Добавьте ИНДЕКСИРОВАТЬ ПО или пересмотрите структуру запроса.
7. Типичные ошибки и как их избежать
Даже опытные разработчики иногда сталкиваются с неожиданными результатами при объединении таблиц. Вот самые распространенные ошибки:
- 🚫 Потеря данных при INNER JOIN: Если не учли, что в правой таблице могут отсутствовать совпадения. Всегда проверяйте количество строк до и после объединения.
- 🚫 Дублирование строк при UNION ALL: Если таблицы перекрываются по ключам, используйте
ОБЪЕДИНИТЬ(безALL) илиГРУППИРОВКА ПО. - 🚫 Несовпадение типов данных: Например, сравниваете
СтрокасЧисло. В 1С это не всегда приводит к ошибке, но может дать некорректный результат. - 🚫 Избыточные JOIN: Соединение пяти таблиц в одном запросе может привести к декартову произведению. Разбивайте запрос на части или используйте временные таблицы.
Пример ошибки с типами данных:
// ОШИБКА: поле Код в одной таблице - строка, в другой - число
ВЫБРАТЬ
Таблица1.Код КАК Код // Строка
ИЗ
#Таблица1 КАК Таблица1
ЛЕВОЕ СОЕДИНЕНИЕ #Таблица2 КАК Таблица2
ПО Таблица1.Код = Таблица2.Код // Число -> нет совпадений!
Решение — привести типы к одному виду:
ВЫБРАТЬ
Таблица1.Код КАК Код
ИЗ
#Таблица1 КАК Таблица1
ЛЕВОЕ СОЕДИНЕНИЕ #Таблица2 КАК Таблица2
ПО ЧИСЛО(Таблица1.Код) = Таблица2.Код
FAQ: Ответы на частые вопросы
Можно ли объединить более двух временных таблиц в одном запросе?
Да, можно использовать цепочку JOIN или несколько UNION ALL. Например:
ВЫБРАТЬ ...
ИЗ
#Таблица1
ЛЕВОЕ СОЕДИНЕНИЕ #Таблица2 ПО ...
ЛЕВОЕ СОЕДИНЕНИЕ #Таблица3 ПО ...
Однако чем больше таблиц, тем выше риск декартова произведения. Для 3+ таблиц лучше использовать временные таблицы с промежуточными результатами.
Почему после UNION ALL данные дублируются?
Это происходит, если в объединяемых таблицах есть одинаковые строки. Чтобы избежать дублирования:
- Используйте
ОБЪЕДИНИТЬвместоОБЪЕДИНИТЬ ВСЕ(удалит дубликаты, но медленнее). - Добавьте уникальный идентификатор (например,
ГУИД() КАК УникальныйКлюч).
Как объединить таблицы, если в них разные типы данных?
Используйте функции приведения типов:
ЧИСЛО(Поле)— для преобразования строки в число.СТРОКА(Поле)— для преобразования числа/даты в строку.ДАТАВРЕМЯ(Поле)— для работы с датами.
Пример: ПО ЧИСЛО(Таблица1.Код) = Таблица2.Код.
Что делать, если запрос с объединением выполняется слишком долго?
Попробуйте следующие шаги:
- Проверьте наличие индексов по полям соединения.
- Разбейте запрос на части с использованием временных таблиц.
- Используйте
ВЫБРАТЬ РАЗРЕШЕННЫЕдля фильтрации данных на уровне СУБД. - Замените
JOINна подзапросы, если правая таблица мала.
Если ничего не помогает, перенесите логику объединения в код 1С (коллекции).
Можно ли объединить временную таблицу с виртуальной таблицей 1С?
Да, временные таблицы можно объединять с любыми источниками данных, поддерживаемыми в запросах 1С, включая:
- Виртуальные таблицы (
Документ.ЗаказПокупателя). - Таблицы базы данных (
Справочник.Номенклатура). - Результаты других запросов (подзапросы).
Пример:
ВЫБРАТЬ
ВТ.Ссылка КАК Заказ,
ВТ.Дата,
ВТ.СуммаДокумента,
ВТТ.Количество КАК КоличествоТоваров
ИЗ
Документ.ЗаказПокупателя КАК ВТ
ЛЕВОЕ СОЕДИНЕНИЕ #ТоварыЗаказов КАК ВТТ
ПО ВТ.Ссылка = ВТТ.Заказ