Разработка интерфейсов на управляемых формах 1С часто преподносит сюрпризы, когда идеально работающий в консоли запрос начинает выдавать странные цифры в динамическом списке. Ситуация, когда вместо ожидаемой суммы 690 рублей система отображает 5070, является классическим симптомом внутренних механизмов работы платформы. В этой статье мы подробно разберем, почему динамический список «врет», и как привести его данные в порядок.
Прежде всего, разберем ситуацию, описанную в коде. Разработчик пытается вычислить сумму по складским ордерам в списке заказов клиентов. Для этого используются временные таблицы, в которых группируются данные по номенклатуре и распоряжениям, а затем результат соединяется с основной таблицей заказов. В консоли запросов такой подход работает, так как она выполняет запрос «как есть». Динамический список же — это сложный объект, который трансформирует исходный текст запроса перед отправкой на СУБД.
Рассмотрим основные причины, по которым данные могут «размножаться» или искажаться:
Документ.ЗаказКлиента.Товары одна и та же номенклатура встречается дважды (например, с разными характеристиками, складами или просто в разных строках), то при соединении по полям Номенклатура и Ссылка данные из временной таблицы прибавятся к результату столько раз, сколько строк с этой номенклатурой найдено в заказе.ПОМЕСТИТЬ ВТ платформа может некорректно накладывать условия отбора или группировки, что приводит к непредсказуемым итогам.ГДЕ. Если у пользователя ограничены права на РасходныйОрдерНаТовары, но есть права на ЗаказКлиента, расчет итогов может «посыпаться» из-за специфики вложенных соединений.Чтобы понять, какой именно запрос уходит на сервер базы данных, недостаточно просто смотреть в конфигуратор. Выясним, как платформа модифицировала наш текст. Для этого воспользуемся встроенными методами динамического списка в модуле формы.
Разберем пример кода для получения реального текста запроса:
// В процедуре ПриСозданииНаСервере или через отдельную команду
Схема = Элементы.Список.ПолучитьИсполняемуюСхемуКомпоновкиДанных();
Настройки = Элементы.Список.ПолучитьИсполняемыеНастройкиКомпоновкиДанных();
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(Схема, Настройки, , , Тип("ГенераторМакетаКомпоновкиДанныхДляКоллекцииЗначений"));
// Теперь в МакетКомпоновки.НаборыДанных.НаборДанных1.Запрос содержится финальный текст
ТекстРеальногоЗапроса = МакетКомпоновки.НаборыДанных.НаборДанных1.Запрос;
Сообщить(ТекстРеальногоЗапроса);
Проанализируем полученный текст. Скорее всего, вы увидите, что платформа добавила дополнительные поля (например, ссылки на версии данных) и обернула ваш запрос в еще один уровень выборки. Если в этом тексте вы видите дублирующиеся условия СОЕДИНЕНИЕ, причина найдена.
Если вы сомневаетесь, что проблема в данных, а не в коде, используем метод «заглушки». В тексте запроса динамического списка заменим вычисляемое поле на константу:
ВЫБРАТЬ
ДокументЗаказКлиента.Ссылка КАК Ссылка,
...
11111 КАК СуммаОрдера
ИЗ
Документ.ЗаказКлиента КАК ДокументЗаказКлиента
Если после этого в списке везде отображается «11111», значит, динамический список работает штатно и проблема исключительно в логике соединений внутри вашего SQL-запроса. Если же даже константа не отображается или отображается не везде — проблема в кэшировании формы или программном переопределении запроса.
Вернемся к примеру автора. Проблема в том, что расчет суммы идет через соединение с табличной частью документа. Рассмотрим правильный подход: расчет должен производиться строго до уровня уникального ключа (в данном случае — ссылки на Заказ Клиента), чтобы исключить «раздувание» строк.
Рекомендуется переписать запрос следующим образом:
Распоряжения (без номенклатуры, если это возможно для расчета суммы).Ссылка заказа.ЛЕВЫМ СОЕДИНЕНИЕМ только по полю Ссылка.Посмотрим на пример оптимизированного участка кода:
// ВТ1: Получаем количество по ордерам в разрезе Номенклатура + Заказ
ВЫБРАТЬ
ТЧ.Распоряжение КАК Заказ,
ТЧ.Номенклатура КАК Номенклатура,
СУММА(ТЧ.Количество) КАК КолОрдер
ПОМЕСТИТЬ ВТ_Ордера
ИЗ
Документ.РасходныйОрдерНаТовары.ТоварыПоРаспоряжениям КАК ТЧ
ГДЕ
ТЧ.Ссылка.Проведен
СГРУППИРОВАТЬ ПО
ТЧ.Распоряжение, ТЧ.Номенклатура;
////////////////////////////////////////////////////////////////
// ВТ2: Считаем сумму, соединяясь с ценами из Заказа
ВЫБРАТЬ
ВТ.Заказ КАК Заказ,
СУММА(ВТ.КолОрдер * ТЧ_Заказа.Цена) КАК СуммаОрдераИтого
ПОМЕСТИТЬ ВТ_ИтоговыеСуммы
ИЗ
ВТ_Ордера КАК ВТ
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.ЗаказКлиента.Товары КАК ТЧ_Заказа
ПО ВТ.Заказ = ТЧ_Заказа.Ссылка И ВТ.Номенклатура = ТЧ_Заказа.Номенклатура
СГРУППИРОВАТЬ ПО
ВТ.Заказ;
Обратите внимание на СГРУППИРОВАТЬ ПО ВТ.Заказ в последней таблице. Это гарантирует, что на выходе для каждого заказа будет ровно одна строка с суммой, независимо от количества позиций номенклатуры.
Хотя временные таблицы (ВТ) разрешены в динамических списках, опытные разработчики рекомендуют их избегать, если запрос планируется использовать в списках с большим количеством данных. Платформа 1С может выполнять запрос к ВТ при каждом скроллинге, что негативно сказывается на производительности.
Альтернативный вариант — использование вложенных запросов. Несмотря на то, что в обычном программировании вложенные запросы считаются менее читаемыми, для оптимизатора динамического списка они часто «прозрачнее», так как позволяют платформе эффективнее встраивать условия отборов в подзапросы.
Выделим ключевые правила, которые помогут избежать расхождения данных:
Основная таблица в настройках динамического списка. Это позволяет платформе корректно работать с объектными правами (RLS) и стандартными командами списка.Ссылка основной таблицы.Регистра сведений. В нем можно хранить уже посчитанные суммы и обновлять их при проведении ордеров. Соединение с одним регистром работает в разы быстрее и надежнее, чем сложные вычисления «на лету».&ДатаАктуальности, передаются в динамический список корректно. Проверьте вкладку «Параметры» в настройках динамического списка на форме.Проанализировав ситуацию с «ерундой» в суммах, мы выяснили, что чудо обычно объясняется математикой соединений. Используйте СГРУППИРОВАТЬ на самом раннем этапе формирования данных и всегда проверяйте исполняемый макет компоновки — это сэкономит часы отладки.