Соединение трёх и более таблиц в 1С:Предприятие — задача, с которой рано или поздно сталкивается каждый разработчик.hether вы формируете сложный отчёт, интегрируете данные из разных справочников или оптимизируете выборку для скорости работы, умение правильно объединять таблицы экономит часы debugging и ресурсы сервера. В этой статье разберём пять рабочих методов соединения трёх таблиц — от базового синтаксиса ОБЪЕДИНИТЬ до нетривиальных приёмов с виртуальными таблицами и СКД (Система Компоновки Данных).
Особенность работы с 1С 8.3 в том, что здесь нет классического SQL-синтаксиса JOIN — вместо этого используется собственный язык запросов с нюансами. Например, соединение ЛЕВОЕ ведёт себя иначе, чем LEFT JOIN в стандартном SQL, а виртуальные таблицы требуют учёта механизма кэширования. Мы покажем, как избежать типичных ошибок (например, потери данных при неверном типе соединения или "размножения" строк из-за неправильных условий) и дадим готовые шаблоны кода для разных сценариев.
1. Базовое соединение трёх таблиц через запрос
Начнём с самого простого варианта — соединения трёх таблиц в одном запросе с использованием конструкции ОБЪЕДИНИТЬ. Этот метод подходит для большинства задач, где нужно получить данные из справочников, документов или регистров сведений, связанных между собой.
Допустим, у нас есть три таблицы:
- 📄 Документ "ЗаказПокупателя" (содержит поля
Номер,Дата,Контрагент) - 🗃️ Справочник "Контрагенты" (поля
Наименование,ИНН,ГруппаКонтрагентов) - 📊 Регистр сведений "ЦеныНоменклатуры" (поля
Номенклатура,Цена,ПериодДействия)
Задача: получить список заказов с названиями контрагентов и актуальными ценами на номенклатуру из заказа. Вот как это сделать:
ВЫБРАТЬ
Заказы.Номер КАК НомерЗаказа,
Заказы.Дата КАК ДатаЗаказа,
Контрагенты.Наименование КАК Контрагент,
Контрагенты.ИНН КАК ИННКонтрагента,
Цены.Цена КАК АктуальнаяЦена
ИЗ
Документ.ЗаказПокупателя КАК Заказы
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Контрагенты КАК Контрагенты
ПО Заказы.Контрагент = Контрагенты.Ссылка
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&ТекущаяДата) КАК Цены
ПО Заказы.Номенклатура = Цены.Номенклатура
ГДЕ
Заказы.Дата МЕЖДУ &НачалоПериода И &КонецПериода
⚠️ Внимание: При использованииСрезПоследних()для регистра сведений убедитесь, что параметр&ТекущаяДатапередаётся корректно. Если дата будет раньше периода действия цены, строка не попадёт в результат, даже если цена существует.
Ключевые моменты этого подхода:
- 🔹 Тип соединения
ЛЕВОЕгарантирует, что все заказы попадут в результат, даже если для них нет данных в справочнике или регистре. - 🔹 Виртуальная таблица
СрезПоследних()автоматически берёт актуальные на дату записи. - 🔹 Условие
ПОдолжно ссылаться на ключевые поля (например,Ссылкадля справочников).
Если в результате появляются дублирующиеся строки, проверьте, нет ли в связанных таблицах нескольких записей с одинаковыми ключами (например, несколько цен на одну номенклатуру с разными периодами действия).
2. Соединение через виртуальные таблицы
Виртуальные таблицы в 1С — мощный инструмент, который позволяет работать с данными регистров накопления, сведений или бухгалтерии без написания сложных запросов. Например, если вам нужно соединить таблицу документов с остатками товаров и оборотом по контрагенту, виртуальные таблицы сэкономят время и ресурсы.
Рассмотрим пример: необходимо получить список реализаций с остатками товаров на складе и суммой взаимозачётов по контрагенту. Для этого используем:
- 📄 Документ
РеализацияТоваровУслуг - 📦 Виртуальная таблица
ОстаткиТоваров(регистр накопления) - 💰 Виртуальная таблица
ОборотыПоКонтрагентам(регистр бухгалтерии)
ВЫБРАТЬ
Реализации.Номер КАК НомерДокумента,
Реализации.Контрагент КАК Контрагент,
Остатки.КоличествоОстаток КАК ОстатокТовара,
Обороты.СуммаОборот КАК СуммаВзаиморасчетов
ИЗ
Документ.РеализацияТоваровУслуг КАК Реализации
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(&КонецДня,
Номенклатура В (
ВЫБРАТЬ РАЗЛИЧНЫЕ РеализацииТовары.Номенклатура
ИЗ Документ.РеализацияТоваровУслуг.Товары КАК РеализацииТовары
ГДЕ РеализацииТовары.Ссылка = Реализации.Ссылка
)
) КАК Остатки
ПО Реализации.Склад = Остатки.Склад
И РеализацииТовары.Номенклатура = Остатки.Номенклатура
ЛЕВОЕ СОЕДИНЕНИЕ РегистрБухгалтерии.ВзаиморасчетыСКонтрагентами.Обороты(
&НачалоПериода, &КонецПериода,
Контрагент В (ВЫБРАТЬ Реализации.Контрагент)
) КАК Обороты
ПО Реализации.Контрагент = Обороты.Контрагент
ГДЕ
Реализации.Дата МЕЖДУ &НачалоПериода И &КонецПериода
Особенности работы с виртуальными таблицами:
| Тип таблицы | Когда использовать | Ограничения |
|---|---|---|
Остатки() |
Нужны текущие остатки на дату | Требует указания измерений (склад, номенклатура) |
Обороты() |
Анализ движения за период | Может быть ресурсоёмким для больших периодов |
СрезПоследних() |
Актуальные на дату записи (например, цены) | Не работает с периодическими регистрами сведений без периода |
⚠️ Внимание: Виртуальные таблицыОбороты()иОстатки()кэшируются на уровне сеанса. Если данные в регистрах изменились после открытия сеанса, результат может быть неактуальным. В таких случаях используйтеОбновитьКэшВиртуальныхТаблиц().
3. Использование временных таблиц для сложных соединений
Когда нужно соединить три таблицы с промежуточной обработкой данных (например, агрегацией или фильтрацией), удобно использовать временные таблицы. Этот метод снижает нагрузку на сервер и упрощает чтение кода.
Пример задачи: получить список заказов, где сумма по документу превышает 100 000 рублей, с разбивкой по менеджерам и категориям номенклатуры. Здесь временные таблицы помогут избежать вложенных запросов:
// Создаём временную таблицу с агрегированными данными по заказам
ВЫБРАТЬ
Заказы.Менеджер КАК Менеджер,
ЗаказыТовары.КатегорияНоменклатуры КАК Категория,
СУММА(ЗаказыТовары.Сумма) КАК СуммаПоКатегории
ПОМЕСТИТЬ ВТ_АгрегацияПоЗаказам
ИЗ
Документ.ЗаказПокупателя КАК Заказы
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.ЗаказПокупателя.Товары КАК ЗаказыТовары
ПО Заказы.Ссылка = ЗаказыТовары.Ссылка
ГДЕ
Заказы.Дата МЕЖДУ &НачалоПериода И &КонецПериода
СГРУППИРОВАТЬ ПО
Заказы.Менеджер,
ЗаказыТовары.КатегорияНоменклатуры
ИМЕЮЩИЕ
СУММА(ЗаказыТовары.Сумма) > 100000
// Основной запрос с соединением временной таблицы и справочников
ВЫБРАТЬ
Агрегация.Менеджер КАК Менеджер,
Справочник.Менеджеры.Наименование КАК ФИОМенеджера,
Агрегация.Категория КАК КатегорияНоменклатуры,
Справочник.КатегорииНоменклатуры.Наименование КАК НаименованиеКатегории,
Агрегация.СуммаПоКатегории КАК Сумма
ИЗ
ВТ_АгрегацияПоЗаказам КАК Агрегация
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Менеджеры КАК Справочник.Менеджеры
ПО Агрегация.Менеджер = Справочник.Менеджеры.Ссылка
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.КатегорииНоменклатуры КАК Справочник.КатегорииНоменклатуры
ПО Агрегация.Категория = Справочник.КатегорииНоменклатуры.Ссылка
Преимущества временных таблиц:
- 📌 Упрощают сложные запросы, разбивая их на логические блоки.
- 📌 Повышают производительность за счёт промежуточной агрегации.
- 📌 Позволяют повторно использовать данные в нескольких запросах.
Определить данные, которые нужно агрегировать заранее|Проверить, что временная таблица не перегружена ненужными полями|Убедиться, что условия фильтрации применены на этапе создания ВТ|Использовать осмысленные имена для временных таблиц (например, ВТ_АгрегацияПоЗаказам)-->
4. Соединение через СКД (Система Компоновки Данных)
Если вам нужно не только получить данные, но и гибко их представить (с группировками, иерархией, динамическими колонками), СКД — оптимальный выбор. В отличие от обычных запросов, СКД позволяет настраивать соединения визуально и управлять выводом без изменения кода.
Рассмотрим пример настройки СКД для соединения трёх таблиц:
- 📂 Документ "ПоступлениеТоваров" (источник данных о поставках).
- 🏢 Справочник "Поставщики" (информация о контрагентах).
- 📈 Регистр накопления "ТоварыНаСкладах" (остатки после поступления).
Шаги настройки СКД:
- Создайте новую схему компоновки данных в конфигураторе.
- Добавьте три набора данных:
- 📌
Поступления— запрос к документуПоступлениеТоваров. - 📌
Поставщики— запрос к справочникуКонтрагентыс фильтром по группе "Поставщики". - 📌
Остатки— виртуальная таблицаОстатки()регистраТоварыНаСкладах.
- 📌
- 🔗
Поступления.Контрагент = Поставщики.Ссылка(тип соединения:Левое). - 🔗
Поступления.Склад = Остатки.Склад И Поступления.Номенклатура = Остатки.Номенклатура(тип:Внутреннее).
Пример кода для создания схемы СКД программно:
СхемаКомпоновкиДанных = Новый СхемаКомпоновкиДанных;
НаборДанныхПоступления = СхемаКомпоновкиДанных.НаборыДанных.Добавить("Поступления");
НаборДанныхПоступления.Запрос.Текст =
"ВЫБРАТЬ
| ПоступлениеТоваров.Номер КАК НомерДокумента,
| ПоступлениеТоваров.Дата КАК Дата,
| ПоступлениеТоваров.Контрагент КАК Контрагент
|ИЗ
| Документ.ПоступлениеТоваров КАК ПоступлениеТоваров
|ГДЕ
| ПоступлениеТоваров.Дата МЕЖДУ &НачалоПериода И &КонецПериода";
// Настройка связи с набором данных "Поставщики"
Связь = СхемаКомпоновкиДанных.СвязиНаборовДанных.Добавить();
Связь.ЛевоеПоле = Новый ПолеНабораДанных("Поступления", "Контрагент");
Связь.ПравоеПоле = Новый ПолеНабораДанных("Поставщики", "Ссылка");
Связь.ТипСвязи = ТипСвязиНаборовДанных.Левое;
⚠️ Внимание: В СКД соединениеПолное(аналогFULL JOIN) может привести к экспоненциальному росту строк в результате, если в таблицах много несвязанных записей. Всегда проверяйте размер выходного набора данных.
Как ускорить работу СКД с большими данными?
Используйте предварительную агрегацию данных во временных таблицах перед передачей их в СКД. Например, если вам нужны только суммы по группам номенклатуры, агрегируйте их заранее в запросе, а не передавайте в СКД детализированные строки. Также отключите ненужные ресурсы в настройках набора данных — это сократит время выполнения.
5. Программное соединение таблиц (без запросов)
Иногда соединение таблиц удобнее выполнять не на уровне запросов, а программно — например, когда нужно динамически формировать связи или обрабатывать данные по сложным алгоритмам. Для этого в 1С можно использовать объекты типа "ТаблицаЗначений" и методы работы с ними.
Пример: соединим три таблицы значений (например, полученные из разных источников) по общему полю КодКонтрагента.
// Исходные таблицы
ТаблицаЗаказов = Новый ТаблицаЗначений;
ТаблицаЗаказов.Колонки.Добавить("КодКонтрагента");
ТаблицаЗаказов.Колонки.Добавить("СуммаЗаказа");
// Таблица с данными контрагентов
ТаблицаКонтрагентов = Новый ТаблицаЗначений;
ТаблицаКонтрагентов.Колонки.Добавить("КодКонтрагента");
ТаблицаКонтрагентов.Колонки.Добавить("Наименование");
// Таблица с историей оплат
ТаблицаОплат = Новый ТаблицаЗначений;
ТаблицаОплат.Колонки.Добавить("КодКонтрагента");
ТаблицаОплат.Колонки.Добавить("СуммаОплаты");
// Соединяем таблицы в одну
Результат = Новый ТаблицаЗначений;
Результат.Колонки.Добавить("КодКонтрагента");
Результат.Колонки.Добавить("НаименованиеКонтрагента");
Результат.Колонки.Добавить("СуммаЗаказа");
Результат.Колонки.Добавить("СуммаОплаты");
// Алгоритм соединения (аналог ЛЕВОГО СОЕДИНЕНИЯ)
Для Каждого СтрокаЗаказа Из ТаблицаЗаказов Цикл
НоваяСтрока = Результат.Добавить();
НоваяСтрока.КодКонтрагента = СтрокаЗаказа.КодКонтрагента;
НоваяСтрока.СуммаЗаказа = СтрокаЗаказа.СуммаЗаказа;
// Ищем контрагента
НайденныйКонтрагент = ТаблицаКонтрагентов.Найти(СтрокаЗаказа.КодКонтрагента, "КодКонтрагента");
Если НайденныйКонтрагент <> Неопределено Тогда
НоваяСтрока.НаименованиеКонтрагента = НайденныйКонтрагент.Наименование;
КонецЕсли;
// Ищем оплаты
НайденнаяОплата = ТаблицаОплат.Найти(СтрокаЗаказа.КодКонтрагента, "КодКонтрагента");
Если НайденнаяОплата <> Неопределено Тогда
НоваяСтрока.СуммаОплаты = НайденнаяОплата.СуммаОплаты;
КонецЕсли;
КонецЦикла;
Когда стоит использовать программное соединение:
- 🔧 Данные поступают из внешних источников (например, Excel, JSON, XML) и требуют предварительной обработки.
- 🔧 Нужно динамически менять условия соединения в зависимости от пользовательского ввода.
- 🔧 Объём данных небольшой (для больших таблиц лучше использовать запросы).
⚠️ Внимание: При программном соединении больших таблиц (более 10 000 строк) может возникнуть зависание интерфейса. В таких случаях используйте ФоновоеЗадание или оптимизируйте алгоритм с помощью индексов.
Программное соединение таблиц гибче запросов, но работает медленнее. Используйте его только когда невозможно обойтись стандартными средствами языка запросов.
6. Типичные ошибки и как их избежать
При соединении трёх таблиц в 1С разработчики часто сталкиваются с одними и теми же проблемами. Разберём самые распространённые ошибки и способы их решения.
Ошибка 1: "Размножение" строк в результате
Симптом: вместо ожидаемых 10 строк в результате появляется 100 или больше. Причина — неверные условия соединения, когда одной строке из первой таблицы соответствует несколько строк во второй или третьей.
Решение:
- 🛠️ Проверьте, что соединение идёт по уникальным ключам (например,
Ссылкадля справочников). - 🛠️ Используйте
РАЗЛИЧНЫЕили агрегирующие функции (МАКСИМУМ,СУММА), если дублирующиеся связи неизбежны.
Ошибка 2: Потеря данных при ЛЕВОМ соединении
Симптом: некоторые строки "пропадают" из результата, хотя должны быть. Причина — неверно указан тип соединения или условие ПО слишком строгое.
Решение:
- 🛠️ Замените
ВНУТРЕННЕЕ СОЕДИНЕНИЕнаЛЕВОЕ, если важны все строки первой таблицы. - 🛠️ Проверьте, что поля в условии
ПОимеют совместимые типы (например, не сравнивайтеСсылкасУникальныйИдентификатор).
Ошибка 3: Медленное выполнение запроса
Симптом: запрос выполняется более 30 секунд. Причины:
- 🐢 Отсутствуют индексы на полях, используемых в условиях
ПОилиГДЕ. - 🐢 Виртуальные таблицы
Обороты()илиОстатки()запрашиваются за большой период. - 🐢 В запросе используются функции над полями в условиях (например,
НАЧИНАЕТСЯ Свместо сравнения по префиксу).
Решение:
- 🚀 Добавьте индексы на часто используемые поля (в конфигураторе, в свойствах таблицы).
- 🚀 Разбейте большой запрос на несколько меньших с использованием временных таблиц.
- 🚀 Замените
Обороты()наОборотыПоПериодам()с указанием конкретных периодов.
| Ошибка | Причина | Решение |
|---|---|---|
| Пустой результат при верных данных | Несовпадение типов полей в условии ПО |
Используйте ЗНАЧЕНИЕ(Поле) для приведения типов |
| Ошибка "Поле не найдено" | Опечатка в имени поля или таблицы | Проверьте регистр и синтаксис в запросе |
| Неправильная сортировка | Отсутствует УПОРЯДОЧИТЬ ПО для связанных полей |
Добавьте сортировку по ключевым полям соединения |
7. Оптимизация соединений для больших баз данных
Если вы работаете с базой, где таблицы содержат сотни тысяч строк, стандартные методы соединения могут работать слишком медленно. В таких случаях поможет:
1. Использование индексов
Индексы ускоряют поиск по полям, используемым в условиях ПО и ГДЕ. В 1С индексы настраиваются в конфигураторе:
- 📌 Откройте свойства таблицы (например, справочника или документа).
- 📌 Перейдите на закладку "Индексы".
- 📌 Добавьте индекс для поля, по которому часто идёт соединение (например,
КонтрагентилиНоменклатура).
2. Разделение запросов
Instead of one complex query with three tables, split it into several simpler ones and combine the results programmatically. For example:
// Запрос 1: получаем список заказов
Запрос1 = Новый Запрос;
Запрос1.Текст =
"ВЫБРАТЬ
| Заказы.Ссылка КАК Ссылка,
| Заказы.Контрагент КАК Контрагент
|ИЗ
| Документ.ЗаказПокупателя КАК Заказы
|ГДЕ
| Заказы.Дата МЕЖДУ &НачалоПериода И &КонецПериода";
РезультатЗаказов = Запрос1.Выполнить().Выгрузить();
// Запрос 2: получаем данные контрагентов для найденных заказов
Запрос2 = Новый Запрос;
Запрос2.Текст =
"ВЫБРАТЬ
| Контрагенты.Ссылка КАК Ссылка,
| Контрагенты.Наименование КАК Наименование
|ИЗ
| Справочник.Контрагенты КАК Контрагенты
|ГДЕ
| Контрагенты.Ссылка В (&СписокКонтрагентов)";
Запрос2.УстановитьПараметр("СписокКонтрагентов", РезультатЗаказов.ВыгрузитьКолонку("Контрагент"));
РезультатКонтрагентов = Запрос2.Выполнить().Выгрузить();
// Соединяем результаты программно
Результат = СоединитьТаблицы(РезультатЗаказов, РезультатКонтрагентов, "Контрагент", "Ссылка");
3. Использование серверных процедур
Для ресурсоёмких операций переносите логику соединения на сервер с помощью ВыполнитьНаСервере() или фоновых заданий. Это разгрузит клиентское приложение и ускорит работу.
Пример оптимизированного серверного метода:
&НаСервере
Функция ПолучитьСоединенныеДанные(НачалоПериода, КонецПериода)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗРЕШЕННЫЕ
| Заказы.Номер КАК НомерЗаказа,
| Контрагенты.Наименование КАК Контрагент,
| СУММА(ЗаказыТовары.Количество) КАК ОбщееКоличество
|ИЗ
| Документ.ЗаказПокупателя КАК Заказы
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Контрагенты КАК Контрагенты
| ПО Заказы.Контрагент = Контрагенты.Ссылка
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.ЗаказПокупателя.Товары КАК ЗаказыТовары
| ПО Заказы.Ссылка = ЗаказыТовары.Ссылка
|ГДЕ
| Заказы.Дата МЕЖДУ &НачалоПериода И &КонецПериода
|СГРУППИРОВАТЬ ПО
| Заказы.Номер,
| Контрагенты.Наименование";
Запрос.УстановитьПараметр("НачалоПериода", НачалоПе