При разработке отчетов в среде 1С:Предприятие 7.7 программисты часто сталкиваются со специфическим поведением языка запросов, так называемых «черных запросов». В отличие от современных версий платформы 1С 8, где связи таблиц прописываются разработчиком вручную с помощью левых и правых соединений, в 1С 7.7 для управления составом выборки используются специальные конструкции в описании группировок: Все и ВошедшиеВЗапрос.
В этой статье мы подробно разберем, что означают эти ключевые слова, как они работают «под капотом» платформы, для решения каких задач они предназначены, а также рассмотрим подводные камни, связанные с производительностью системы.
Рассмотрим стандартный запрос, который выбирает данные из регистра остатков или оборотов. Если мы пишем обычную группировку без дополнительных ключевых слов:
|Группировка Номенклатура Без Групп;
|Группировка Комиссионер Без Групп;
В этом случае система вернет только те комбинации номенклатуры и контрагентов (комиссионеров), по которым в базе данных реально существуют движения или остатки. Если по какому-то контрагенту за выбранный период не было операций, он вообще не попадет в выборку запроса.
Если же мы модифицируем запрос и добавим ключевое слово Все:
|Группировка Комиссионер Без Групп Все;
Ситуация кардинально меняется. Теперь платформа принудительно включит в результат запроса абсолютно все элементы справочника контрагентов, которые удовлетворяют условиям (если они наложены на сам справочник). При этом для тех контрагентов, по которым в регистре не было никаких движений, числовые показатели (ресурсы запроса, такие как сумма или количество) будут равны нулю.
Иными словами, ключевое слово Все заставляет систему брать за основу не таблицу движений регистра, а саму таблицу справочника, указанного в группировке, и «накладывать» на нее данные регистра. Это аналог SQL-конструкции LEFT OUTER JOIN, где левой таблицей выступает весь справочник.
Часто на практике полный справочник контрагентов или номенклатуры не нужен (он может содержать десятки тысяч позиций, большинство из которых не используются). Нам требуется получить отчет, в котором будут фигурировать только те объекты, по которым за выбранный период были хоть какие-то движения, но при этом структура отчета должна оставаться строго фиксированной.
Для решения этой задачи применяется связка Все ВошедшиеВЗапрос. Например:
|Группировка Комиссионер Без Групп Все ВошедшиеВЗапрос;
Этот механизм работает следующим образом:
Классический пример использования ключевого слова ВошедшиеВЗапрос — это построение «шахматки» (кросс-таблицы), где в строках выводится номенклатура, а в колонках вправо строятся покупатели.
Если обходить выборку обычным способом, то для каждого товара будет выводиться свой собственный список покупателей. Это сделает невозможным вывод отчета простым присоединением секций таблицы в цикле, так как колонки будут постоянно смещаться. Использование Все ВошедшиеВЗапрос гарантирует, что для каждой строки номенклатуры состав колонок-покупателей будет абсолютно идентичным.
Рассмотрим пример программного кода для реализации такого отчета:
Процедура СформироватьШахматку()
Запрос = СоздатьОбъект("Запрос");
ТекстЗапроса = "
|Период с ВыбНачПериода по ВыбКонПериода;
|Номенклатура = Регистр.Продажи.Номенклатура;
|Покупатель = Регистр.Продажи.Покупатель;
|Выручка = Регистр.Продажи.ПродСтоимость;
|Функция ВсегоВыручка = Сумма(Выручка);
|Группировка Номенклатура Без Групп;
|Группировка Покупатель Без Групп Все ВошедшиеВЗапрос;
|Условие(Номенклатура в ВыбТМЦ);
|";
Если Запрос.Выполнить(ТекстЗапроса) = 0 Тогда
Предупреждение("Ошибка выполнения запроса!");
Возврат;
КонецЕсли;
Таб = СоздатьОбъект("Таблица");
Таб.ВывестиСекцию("Шапка_ЛеваяЧасть"); // Выводит ячейку "Товар / Контрагент"
// Шаг 1. Заполняем горизонтальную шапку отчета (колонки покупателей)
// Для этого временно обходим вторую группировку, чтобы вывести имена клиентов
Пока Запрос.Группировка(2) = 1 Цикл
Таб.ПрисоединитьСекцию("Шапка_КолонкаПокупателя");
КонецЦикла;
// Шаг 2. Выводим строки с данными
Пока Запрос.Группировка(1) = 1 Цикл
// Выводим левую колонку строки с названием товара
Таб.ВывестиСекцию("Строка_Товар");
// Обходим покупателей. Благодаря 'Все ВошедшиеВЗапрос'
// для каждого товара обход вернет один и тот же набор покупателей
Пока Запрос.Группировка(2) = 1 Цикл
СуммаПродажи = Запрос.ВсегоВыручка; // Здесь будет реальная сумма или 0
Таб.ПрисоединитьСекцию("Строка_ЯчейкаДанных");
КонецЦикла;
КонецЦикла;
Таб.Показать("Шахматка продаж");
КонецПроцедуры
Несмотря на удобство построения кросс-отчетов, использование ключевых слов Все и ВошедшиеВЗапрос имеет ряд серьезных ограничений и может негативно сказаться на производительности информационной базы.
1. Работа с временными интервалами. Ключевое слово Все можно использовать не только для справочников, но и для временных группировок (Период, Год, Месяц, Декада, Неделя, День). Например, конструкция Группировка День Все; гарантирует, что в выборку запроса попадут абсолютно все календарные дни из указанного в запросе периода, даже если в эти дни в системе не было зарегистрировано ни одного документа.
2. Взаимодействие с оператором «Условие». Поведение запроса существенно зависит от того, на какое именно поле наложено условие:
Все (например, Условие(Номенклатура в ВыбГруппа) при группировке по номенклатуре), запрос вернет только элементы этой группы.Условие(Склад = ВыбСклад)), а группировка идет по Номенклатура Все, запрос все равно выберет весь справочник номенклатуры, заполнив нулевыми значениями все товары, по которым не было движений на выбранном складе. На больших справочниках это приводит к генерации гигантских нечитаемых отчетов.3. Проблема падения производительности (SQL-версии). Движок запросов 1С 7.7 не оптимизирован для трансляции сложных группировок со словом Все в эффективный SQL-код. Вместо выполнения одного быстрого запроса СУБД начинает строить декартово произведение (Cartesian Product) таблиц в оперативной памяти сервера или через временные таблицы на диске. Если в справочнике номенклатуры содержится более 10 000 элементов, а в справочнике контрагентов — более 5 000, выполнение такого запроса может полностью заблокировать работу базы данных или привести к аварийному завершению процесса из-за нехватки памяти.
Чтобы избежать зависания системы на больших объемах данных, опытные программисты 1С 7.7 отказывались от использования штатного механизма Все в пользу альтернативных вариантов:
ТаблицаЗначений. Затем программист создавал в коде алгоритм ручного дополнения этой таблицы недостающими строками и колонками из справочников. Этот способ работал в десятки раз быстрее штатного механизма.1С++ и класса ODBCRecordset разработчики писали прямые T-SQL запросы к SQL-серверу, реализуя левые соединения и эмуляцию кросс-таблиц на уровне СУБД, что позволяло получать результаты мгновенно.В современных версиях платформы 1С v8.3 разработчики полностью отказались от неоднозначных конструкций Все и ВошедшиеВЗапрос. Теперь эти задачи решаются на качественно другом уровне:
ЛЕВОЕ СОЕДИНЕНИЕ.РезультатЗапроса.Выбрать() поддерживает специальный параметр ГруппировкиДляЗначенийГруппировок, который позволяет получить все значения подчиненной группировки, вошедшие в результат.