Работа с языком запросов 1С — это базовый навык любого разработчика. Однако даже опытные специалисты иногда сталкиваются со странным поведением СУБД, когда вместо красивой сгруппированной таблицы с итогами по документам запрос возвращает детальные «построчные» записи. Ситуация усложняется, когда при попытке исправить запрос суммы начинают многократно увеличиваться, превращая результат в хаос.
В рамках этой статьи мы подробно разберем, почему возникают подобные проблемы, проанализируем ошибки разработчиков из обсуждения и выработаем оптимальные шаблоны запросов в соответствии со стандартами разработки фирмы «1С».
Давайте посмотрим на код, который автор темы привел в качестве неудачной попытки решить задачу самостоятельно:
ВЫБРАТЬ
РеализацияТоваровУслугТовары.Ссылка.Номер,
РеализацияТоваровУслугТовары.Ссылка,
РеализацияТоваровУслугТовары.Ссылка.Дата КАК Дата,
РеализацияТоваровУслугТовары.Ссылка.Склад КАК Склад,
СУММА(РеализацияТоваровУслугТовары.Количество) КАК Количество,
СУММА(РеализацияТоваровУслугТовары.Сумма) КАК Сумма,
СУММА(РеализацияТоваровУслугТовары.СуммаНДС) КАК СуммаНДС
ИЗ
Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТоваровУслугТовары
ГДЕ
РеализацияТоваровУслугТовары.Ссылка.Дата МЕЖДУ &ДатаНачала И &ДатаКонца
И РеализацияТоваровУслугТовары.Ссылка.Проведен
И РеализацияТоваровУслугТовары.Номенклатура В ИЕРАРХИИ(&Номенклатура)
СГРУППИРОВАТЬ ПО
РеализацияТоваровУслугТовары.Ссылка,
РеализацияТоваровУслугТовары.Количество, // ВНИМАНИЕ: Ошибка здесь!
РеализацияТоваровУслугтовары.Ссылка.Номер,
РеализацияТоваровУслугТовары.Ссылка.Дата,
РеализацияТоваровУслугТовары.Ссылка.Склад,
РеализацияТоваровУслугТовары.Сумма, // ВНИМАНИЕ: Ошибка здесь!
РеализацияТоваровУслугТовары.СуммаНДС // ВНИМАНИЕ: Ошибка здесь!
Выясним причину, почему этот запрос продолжает возвращать детальные строки. Обратите внимание на секцию СГРУППИРОВАТЬ ПО. Автор включил в нее поля Количество, Сумма и СуммаНДС.
Важное правило построения запросов: в секцию СГРУППИРОВАТЬ ПО должны входить только те поля, по которым мы хотим объединить строки (измерения группировки). Если мы добавляем туда числовые поля, которые планируем суммировать, СУБД будет группировать строки только при совпадении этих чисел.
Например, если в документе три строки с количеством «1», «5» и «10», то СУБД вернет три отдельные строки, так как значения группировочного поля различаются. Агрегатная функция СУММА() в этом случае становится абсолютно бесполезной. Чтобы исправить эту ошибку, необходимо полностью исключить суммируемые ресурсы из секции группировки.
Если мы обратим внимание на поля выбора в запросах участников форума, то увидим конструкцию вида РеализацияТоваровУслугТовары.Ссылка.Номер. Это называется неявным разыменованием ссылочного поля.
На уровне СУБД каждое обращение «через точку» транслируется в левое соединение (LEFT JOIN) с таблицей шапки документа. Поскольку запрос выполняется к табличной части (в которой строк может быть в десятки раз больше, чем самих документов), СУБД вынуждена выполнять соединение таблиц для каждой строки ТЧ еще до того, как сработает группировка. На больших объемах данных это гарантированно приведет к существенному замедлению работы системы.
Чтобы избежать этой проблемы, фирма «1С» рекомендует использовать механизм временных таблиц: сначала выбрать и сгруппировать данные по физическому ключу (полю Ссылка), а затем соединить полученный результат с таблицей шапки документа.
В сообщении №26 автор пишет: «построчно не выводит теперь, но неверно считает количество и сумму, увеличивает их многократно». Это классический пример декартова произведения таблиц (Cartesian Product).
Разберем, почему это произошло. Автор попытался использовать временную таблицу ВТ_СписокДокументов, отобрав туда документы, содержащие нужный товар. Но при создании этой ВТ он забыл сгруппировать ссылки или использовать ключевое слово РАЗЛИЧНЫЕ. В результате, если в одном документе было 5 строк с искомым товаром, ссылка на этот документ попала во временную таблицу 5 раз.
При последующем соединении основной таблицы с этой ВТ по полю Ссылка произошло размножение строк: каждая строка документа соединилась с пятью одинаковыми строками из временной таблицы. Итоговая сумма увеличилась ровно в 5 раз.
Чтобы избежать «размножения» данных при соединениях, всегда убеждайтесь, что таблица, к которой вы присоединяетесь, содержит уникальные ключевые поля (в данном случае уникальные ссылки документов).
В начале обсуждения автор пытался решить задачу с помощью конструкции ИТОГИ СУММА(...) ПО Ссылка, но результат все равно выводился построчно. Давайте разберем разницу между этими операторами:
СГРУППИРОВАТЬ ПО — это директива для СУБД, которая «схлопывает» исходные строки. На выходе мы получаем плоскую таблицу, где детальные записи ТЧ уничтожаются, а вместо них остаются только агрегированные данные.ИТОГИ ПО — это инструмент платформы 1С для построения иерархических структур (деревьев). Сам запрос при этом возвращает как детальные записи (каждую строку товара отдельно), так и расчетные строки итогов по группировочным полям.Если мы используем конструкцию ИТОГИ, обход результата запроса в коде на встроенном языке должен выполняться строго с указанием типа обхода ПоГруппировкам. Посмотрим на правильный пример программной обработки итогов:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| РеализацияТовары.Ссылка КАК Ссылка,
| РеализацияТовары.Номенклатура КАК Номенклатура,
| РеализацияТовары.Количество КАК Количество,
| РеализацияТовары.Сумма КАК Сумма
|ИЗ
| Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТовары
|ГДЕ
| РеализацияТовары.Ссылка.Дата МЕЖДУ &ДатаНачала И &ДатаКонца
|ИТОГИ
| СУММА(Количество),
| СУММА(Сумма)
|ПО
| Ссылка";
РезультатЗапроса = Запрос.Выполнить();
// Выбираем только верхний уровень иерархии (документы)
ВыборкаДокументов = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаДокументов.Следующий() Цикл
// Здесь доступны только общие суммы по документу в целом
Сообщить("Документ: " + ВыборкаДокументов.Ссылка + " Итого сумма: " + ВыборкаДокументов.Сумма);
// Если нам нужны строки товаров внутри этого документа, открываем подчиненный обход:
ВыборкаДеталей = ВыборкаДокументов.Выбрать();
Пока ВыборкаДеталей.Следующий() Цикл
// Здесь мы видим каждую строку ТЧ отдельно
Сообщить(" --> Товар: " + ВыборкаДеталей.Номенклатура + " Кол-во: " + ВыборкаДеталей.Количество);
КонецЦикла;
КонецЦикла;
Если же обойти результат запроса обычным методом Выбрать() без параметров (или со значением Прямой), то платформа выдаст сплошную ленту из детальных строк и промежуточных итогов, что визуально и покажется разработчику «построчным выводом» без суммирования.
В зависимости от того, какую бизнес-задачу мы решаем, правильная архитектура запроса будет различаться. Рассмотрим два основных сценария.
Этот вариант используется, если в отчет должны попасть только показатели по определенному списку номенклатуры, свернутые до документа.
Рассмотрим оптимальный запрос с использованием временных таблиц для исключения разыменования полей через точку:
// Шаг 1: Агрегируем данные на уровне табличной части
ВЫБРАТЬ
РеализацияТовары.Ссылка КАК Ссылка,
СУММА(РеализацияТовары.Количество) КАК Количество,
СУММА(РеализацияТовары.Сумма) КАК Сумма,
СУММА(РеализацияТовары.СуммаНДС) КАК СуммаНДС
ПОМЕСТИТЬ ВТ_СуммыПоТоварам
ИЗ
Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТовары
ГДЕ
РеализацияТовары.Ссылка.Дата МЕЖДУ &ДатаНачала И &ДатаКонца
И РеализацияТовары.Ссылка.Проведен = ИСТИНА
И РеализацияТовары.Номенклатура В ИЕРАРХИИ(&Номенклатура)
СГРУППИРОВАТЬ ПО
РеализацияТовары.Ссылка
;
////////////////////////////////////////////////////////////////////////////////
// Шаг 2: Получаем реквизиты шапки через быстрое соединение с индексами
ВЫБРАТЬ
ВТ.Ссылка КАК Ссылка,
ДокументРеализация.Номер КАК Номер,
ДокументРеализация.Дата КАК Дата,
ДокументРеализация.Склад КАК Склад,
ВТ.Количество КАК Количество,
ВТ.Сумма КАК Сумма,
ВТ.СуммаНДС КАК СуммаНДС
ИЗ
ВТ_СуммыПоТоварам КАК ВТ
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг КАК ДокументРеализация
ПО ВТ.Ссылка = ДокументРеализация.Ссылка
Часто требуется другая логика: мы ищем документы, в которых продавался определенный товар, но в итоге хотим показать общую сумму всего документа по всем позициям.
В этом случае нам вообще не нужно суммировать строки ТЧ самостоятельно! У любого документа в шапке есть готовые реквизиты СуммаДокумента, которые рассчитываются платформой автоматически при проведении. Нам достаточно отобрать уникальные ссылки и соединить их с шапкой:
// Шаг 1: Отбираем только уникальные ссылки документов
ВЫБРАТЬ РАЗЛИЧНЫЕ
РеализацияТовары.Ссылка КАК Ссылка
ПОМЕСТИТЬ ВТ_ОтобранныеДокументы
ИЗ
Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТовары
ГДЕ
РеализацияТовары.Ссылка.Дата МЕЖДУ &ДатаНачала И &ДатаКонца
И РеализацияТовары.Ссылка.Проведен = ИСТИНА
И РеализацияТовары.Номенклатура В ИЕРАРХИИ(&Номенклатура)
;
////////////////////////////////////////////////////////////////////////////////
// Шаг 2: Получаем готовые общие суммы из реквизитов шапки документа
ВЫБРАТЬ
ВТ.Ссылка КАК Ссылка,
ДокументРеализация.Номер КАК Номер,
ДокументРеализация.Дата КАК Дата,
ДокументРеализация.Склад КАК Склад,
ДокументРеализация.СуммаДокумента КАК ОбщаяСуммаДокумента
ИЗ
ВТ_ОтобранныеДокументы КАК ВТ
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг КАК ДокументРеализация
ПО ВТ.Ссылка = ДокументРеализация.Ссылка
Использование этого подхода позволяет сократить нагрузку на сервер СУБД в несколько раз, так как исключаются ресурсоемкие математические вычисления по строкам многострочных частей документов.