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