В процессе разработки отчетов в 1С часто возникает задача анализа «дублей» или пересекающихся данных. Рассмотрим типичную ситуацию: необходимо вывести список заказов покупателей, которые были ошибочно или намеренно включены в два и более документа МаршрутныйЛист. При этом отчет должен показывать не просто количество таких вхождений, а конкретные ссылки на сами маршрутные листы.
Эта задача интересна тем, что требует понимания механизмов группировки и соединений в языке запросов 1С. Разберем ситуацию поэтапно, проанализируем различные подходы и выясним, как платформа (особенно старые версии, такие как 8.0) влияет на написание кода.
Для начала проанализируем структуру данных. У нас есть документ МаршрутныйЛист, в котором имеется табличная часть (например, Заказы), где содержатся ссылки на ЗаказПокупателя. Нам нужно отобрать те заказы, которые встречаются в этой табличной части разных документов более одного раза. Простая выборка из табличной части не даст результата, так как она выведет все строки. Обычная группировка по заказам позволит посчитать количество, но «схлопнет» ссылки на маршрутные листы, и мы не увидим, в какие именно документы попал заказ.
Рассмотрим правильный алгоритм решения задачи:
Если вы работаете на современной платформе (начиная с 8.1), наиболее эффективным и читаемым способом будет использование временных таблиц. Это позволяет разделить логику на два понятных шага.
Рассмотрим пример кода:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| МаршрутныйЛистЗаказы.Заказ КАК Заказ,
| КОЛИЧЕСТВО(РАЗЛИЧНЫЕ МаршрутныйЛистЗаказы.Ссылка) КАК КоличествоЛистов
|ПОМЕСТИТЬ ВТ_ПроблемныеЗаказы
|ИЗ
| Документ.МаршрутныйЛист.Заказы КАК МаршрутныйЛистЗаказы
|ГДЕ
| МаршрутныйЛистЗаказы.Заказ.Дата МЕЖДУ &НачалоПериода И &КонецПериода
|СГРУППИРОВАТЬ ПО
| МаршрутныйЛистЗаказы.Заказ
|ИМЕЮЩИЕ
| КОЛИЧЕСТВО(РАЗЛИЧНЫЕ МаршрутныйЛистЗаказы.Ссылка) >= 2;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ_ПроблемныеЗаказы.Заказ КАК Заказ,
| МаршрутныйЛистЗаказы.Ссылка КАК Маршрутник
|ИЗ
| ВТ_ПроблемныеЗаказы КАК ВТ_ПроблемныеЗаказы
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.МаршрутныйЛист.Заказы КАК МаршрутныйЛистЗаказы
| ПО ВТ_ПроблемныеЗаказы.Заказ = МаршрутныйЛистЗаказы.Заказ";
Запрос.УстановитьПараметр("НачалоПериода", НачалоПериода);
Запрос.УстановитьПараметр("КонецПериода", КонецПериода);
Результат = Запрос.Выполнить();
Важные моменты в этом решении:
ИМЕЮЩИЕ (HAVING): он позволяет фильтровать данные уже после того, как они были сгруппированы. Это критически важно для отбора по количеству.РАЗЛИЧНЫЕ внутри КОЛИЧЕСТВО: он гарантирует, что мы считаем именно количество разных документов. Если один и тот же заказ дважды указан в одном маршрутном листе (ошибка заполнения), такой заказ не должен считаться «проблемным» только по этой причине.Как выяснилось в ходе обсуждения на форуме, в версии 1С 8.0 отсутствует поддержка временных таблиц (ключевое слово ПОМЕСТИТЬ вызовет синтаксическую ошибку). В этом случае нам придется использовать вложенный запрос. Логика остается той же, но структура запроса меняется.
Проанализируем пример кода для версии 8.0:
"ВЫБРАТЬ
| МаршрутныйЛистЗаказы.Ссылка КАК Маршрутник,
| ВложенныйЗапрос.Заказ КАК Заказ
|ИЗ
| (ВЫБРАТЬ
| МаршрутныйЛистЗаказы.Заказ КАК Заказ
| ИЗ
| Документ.МаршрутныйЛист.Заказы КАК МаршрутныйЛистЗаказы
| ГДЕ
| МаршрутныйЛистЗаказы.Заказ.Дата МЕЖДУ &НачалоПериода И &КонецПериода
| СГРУППИРОВАТЬ ПО
| МаршрутныйЛистЗаказы.Заказ
| ИМЕЮЩИЕ
| КОЛИЧЕСТВО(РАЗЛИЧНЫЕ МаршрутныйЛистЗаказы.Ссылка) >= 2) КАК ВложенныйЗапрос
| ЛЕВОЕ СОЕДИНЕНИЕ Документ.МаршрутныйЛист.Заказы КАК МаршрутныйЛистЗаказы
| ПО ВложенныйЗапрос.Заказ = МаршрутныйЛистЗаказы.Заказ"
Обратите внимание, что здесь вся выборка происходит в один прием. Сначала СУБД выполняет ВложенныйЗапрос, находит список ссылок на заказы, а затем соединяет его с табличной частью документа.
В процессе обсуждения были затронуты важные аспекты производительности. Выясним, почему некоторые подходы работают медленно и как это исправить.
1. Отказ от обращения через точку в полях группировки.
В тексте запроса часто встречается конструкция МаршрутныйЛистЗаказы.Заказ.Ссылка. С точки зрения 1С, поле Заказ в табличной части — это уже ссылка. Обращение .Ссылка является избыточным (разыменование ссылки). Еще хуже, когда через точку вызывают реквизиты, например Заказ.Дата. Каждая такая точка — это неявное левое соединение (LEFT JOIN) на уровне SQL. Если записей тысячи, запрос будет тормозить. Рассмотрим правильный вариант:
Заказ.2. Запрос в цикле — главная ошибка.
В одном из сообщений (№52) предлагалось выполнить первый запрос для получения списка заказов, а затем в цикле Пока Выборка.Следующий() выполнять второй запрос для каждого заказа. Никогда не делайте так. Рассмотрим причину: если у вас 5000 заказов, система сделает 5001 обращение к базе данных. Это создаст колоссальную нагрузку. Правильный путь — всегда использовать соединения (JOIN) или временные таблицы внутри одного запроса.
3. Группировка по всем полям.
Помните, что если вы используете СГРУППИРОВАТЬ ПО, все поля, которые вы выбираете в секции ВЫБРАТЬ, должны либо входить в группировку, либо быть агрегатными функциями (СУММА, КОЛИЧЕСТВО и т.д.). Именно поэтому мы не можем вывести МаршрутныйЛист.Ссылка в том же запросе, где считаем количество заказов — это вернет нам "1" для каждой строки, так как группировка по ссылке сделает каждую строку уникальной.
После того как мы получили результат запроса, его нужно вывести в макет. Рассмотрим пример кода обработки результата:
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
ОбластьДанные.Параметры.Заказ = Выборка.Заказ;
ОбластьДанные.Параметры.Маршрутник = Выборка.Маршрутник;
ТабДок.Вывести(ОбластьДанные);
КонецЦикла;
Если заказов много, рекомендуется также использовать иерархический вывод или группировки в табличном документе, чтобы визуально объединить несколько маршрутных листов под одним заголовком заказа. Это сделает отчет более читаемым для пользователя.
Проанализировав ситуацию, мы видим, что даже в старых версиях платформы 1С задача решается элегантно через вложенные запросы, а понимание работы агрегатных функций и соединений позволяет избежать типичных ошибок производительности.