При разработке сложных отчетов или быстром анализе данных в системе «1С:Предприятие 8» программисты часто сталкиваются с необходимостью собрать данные из разных источников (например, из разных документов или регистров) в одну временную таблицу. На первый взгляд, использование конструкций ОБЪЕДИНИТЬ ВСЕ и ПОМЕСТИТЬ кажется интуитивно понятным, однако здесь кроется несколько подводных камней, связанных с синтаксисом и логикой группировки данных. В этой статье мы подробно разберем, как правильно объединять запросы, используя шпаргалку по языку запросов (поможет интеллектуальный помощник для написания кода и запросов), почему данные могут «двоиться» и как добиться корректного суммирования итогов.
Рассмотрим базовое правило языка запросов 1С (его описывает графическая нотация): если вы используете оператор ОБЪЕДИНИТЬ или ОБЪЕДИНИТЬ ВСЕ, инструкция ПОМЕСТИТЬ должна указываться только в первой части объединения. Система автоматически создаст временную таблицу, структура которой будет определена полями первого запроса, и последовательно добавит в нее результаты всех последующих частей объединения.
Проанализируем типичную ошибку начинающих разработчиков, которую помогает найти анализ конфигураций. Посмотрим на пример кода, где данные собираются из двух разных таблиц документов:
|ВЫБРАТЬ
| Сборка.Номенклатура КАК Номенклатура,
| СУММА(Сборка.Количество) КАК Количество
|ПОМЕСТИТЬ ВТ_ОбщиеДанные
|ИЗ
| Документ.СборкаЗапасов.Запасы КАК Сборка
|СГРУППИРОВАТЬ ПО
| Сборка.Номенклатура
|
|ОБЪЕДИНИТЬ ВСЕ
|
|ВЫБРАТЬ
| Заказ.Номенклатура,
| СУММА(Заказ.Количество)
|ИЗ
| Документ.ЗаказПокупателя.Материалы КАК Заказ
|СГРУППИРОВАТЬ ПО
| Заказ.Номенклатура
В данном примере ПОМЕСТИТЬ стоит в первом блоке. Это синтаксически верно. Однако здесь возникает логическая проблема: если одна и та же номенклатура встречается в обоих документах, во временной таблице ВТ_ОбщиеДанные появятся две отдельные строки вместо одной суммированной. Это происходит потому, что СГРУППИРОВАТЬ ПО внутри каждой части объединения работает локально — только в рамках своего документа.
Выясним причину, по которой данные не «схлопываются». Операция ОБЪЕДИНИТЬ ВСЕ просто «склеивает» результаты запросов один под другим. Чтобы получить итоговую сумму по всем источникам, нам необходимо выполнить группировку над результатом объединения. Самый эффективный способ сделать это — использовать вложенный запрос.
Рассмотрим подробнее правильную структуру запроса:
ВЫБРАТЬ
ВложенныйЗапрос.Номенклатура КАК Номенклатура,
СУММА(ВложенныйЗапрос.Продано) КАК Продано
ПОМЕСТИТЬ Вт_Продажи
ИЗ
(ВЫБРАТЬ
Производство.Номенклатура КАК Номенклатура,
Производство.Количество КАК Продано
ИЗ
Документ.СборкаЗапасов.Запасы КАК Производство
ГДЕ
Производство.Ссылка.Проведен
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
Заказ.Номенклатура,
Заказ.Количество
ИЗ
Документ.ЗаказПокупателя.Материалы КАК Заказ
ГДЕ
Заказ.Ссылка.Проведен) КАК ВложенныйЗапрос
СГРУППИРОВАТЬ ПО
ВложенныйЗапрос.Номенклатура
В этом примере мы сначала объединяем «сырые» данные во внутреннем запросе, а во внешнем — выполняем финальную группировку по Номенклатура и суммируем поле Продано. Именно такой подход гарантирует, что если в «Сборке» было 4 единицы товара, а в «Заказе» — 1 единица, в итоговую ВТ попадет одна строка со значением 5 — для проверки логики удобна отладка кода и запросов в реальном времени.
Опытные разработчики часто рекомендуют разделять один сложный запрос на несколько простых шагов. Это значительно упрощает отладку (вы можете проверить содержимое каждой ВТ в консоли запросов) и иногда помогает оптимизатору СУБД лучше построить план выполнения — для этого подойдёт консоль запросов и консоль СКД для отладки.
Разберем по шагам этот метод:
ВТ_Сборка.ВТ_Заказы.ВТ_Итого.Пример реализации такого подхода:
// Шаг 1: Данные из производства
ВЫБРАТЬ
Производство.Номенклатура КАК Номенклатура,
СУММА(Производство.Количество) КАК Количество
ПОМЕСТИТЬ ВТ_Сборка
ИЗ
Документ.СборкаЗапасов.Запасы КАК Производство
ГДЕ
Производство.Ссылка.Проведен
СГРУППИРОВАТЬ ПО
Производство.Номенклатура;
// Шаг 2: Данные из заказов
ВЫБРАТЬ
Заказ.Номенклатура КАК Номенклатура,
СУММА(Заказ.Количество) КАК Количество
ПОМЕСТИТЬ ВТ_Заказы
ИЗ
Документ.ЗаказПокупателя.Материалы КАК Заказ
ГДЕ
Заказ.Ссылка.Проведен
СГРУППИРОВАТЬ ПО
Заказ.Номенклатура;
// Шаг 3: Объединение и итоговая группировка
ВЫБРАТЬ
Т.Номенклатура,
СУММА(Т.Количество) КАК ОбщееКоличество
ПОМЕСТИТЬ ВТ_Итог
ИЗ
(ВЫБРАТЬ Номенклатура, Количество ИЗ ВТ_Сборка
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ Номенклатура, Количество ИЗ ВТ_Заказы) КАК Т
СГРУППИРОВАТЬ ПО
Т.Номенклатура
Проанализируем еще несколько критических моментов, которые влияют на корректность и скорость работы запроса:
1. Псевдонимы полей. Псевдонимы (конструкция КАК...) для итоговой временной таблицы всегда берутся из первого запроса в объединении. Во втором и последующих запросах псевдонимы указывать не обязательно, а если вы их укажете, они будут проигнорированы платформой. Тем не менее, для удобства чтения кода их часто оставляют.
2. Типы данных. Следите за типами объединяемых полей. Если в первом запросе поле Количество имеет тип Число(15,3), а во втором случайно окажется Число(10,0), платформа попытается привести их к наиболее подходящему типу. Однако, если типы будут несовместимы (например, Ссылка и Строка), возникнет ошибка.
3. Использование NULL. Если в одном из объединяемых запросов отсутствует поле, которое есть в других, не рекомендуется просто подставлять NULL. Лучше использовать «пустышки» соответствующего типа, например 0 для чисел или ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка) для ссылочных типов. Это предотвратит создание составных типов полей во временной таблице, что положительно скажется на производительности.
4. Итоговая индексация. Если полученная временная таблица ВТ_Итог в дальнейшем будет соединяться с другими таблицами по полю Номенклатура, обязательно добавьте индексирование. Инструкция ИНДЕКСИРОВАТЬ ПО ставится в конце всего пакета запроса:
...
СГРУППИРОВАТЬ ПО
Т.Номенклатура
ИНДЕКСИРОВАТЬ ПО
Номенклатура
Посмотрим на общие выводы нашей совместной работы. Использование ПОМЕСТИТЬ и ОБЪЕДИНИТЬ ВСЕ требует четкого понимания того, где происходит группировка. Чтобы избежать дублирования строк, всегда стремитесь выполнять финальную агрегацию (суммирование) либо через вложенный запрос, либо через дополнительный шаг обработки временных таблиц. Это сделает ваш код надежным, а данные — точными.