В процессе разработки отчетов или форм документов в 1С мы часто сталкиваемся с задачей, когда из одного запроса мы получаем несколько строк данных, которые по своей сути относятся к одной логической записи, но различаются по одному или нескольким полям. Типичным примером является ситуация, когда необходимо вывести все этапы производства для конкретной номенклатуры в рамках одного заказа, но не в виде отдельных строк, а в виде объединенного списка в одной ячейке табличной части. Рассмотрим, как эффективно решить эту задачу в 1С.
Представим, что у нас есть запрос к регистру накопления "ЭтапыПроизводства", который возвращает остатки по номенклатуре и заказу, включая отдельный реквизит "Этап". Если для одной и той же номенклатуры по одному заказу есть несколько различных этапов, запрос вернет несколько отдельных строк. Наша цель — представить эти данные таким образом, чтобы для каждой уникальной комбинации "ЗаказНаПроизводство" + "Номенклатура" + "Характеристика" все связанные "Этапы" были собраны в одну ячейку в итоговой табличной части.
Давайте проанализируем исходный запрос, предоставленный вами:
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
| ЗаказыОстатки.ЗаказНаПроизводство КАК ЗаказНаПроизводство,
| ЗаказыОстатки.Номенклатура КАК Номенклатура,
| ЗаказыОстатки.Характеристика КАК Характеристика,
| СУММА(ЗаказыОстатки.КоличествоОстаток) КАК Количество,
| ЗаказыОстатки.Этап КАК Этап,
| ЗаказыОстатки.Номенклатура.ТипНоменклатуры КАК ТипНоменклатуры,
| ЗаказыОстатки.ЕдиницаИзмерения КАК ЕдиницаИзмерения,
| ЗаказыОстатки.Спецификация КАК Спецификация,
| ЗаказыОстатки.ПодразделениеЗавершающегоЭтапа КАК ПодразделениеЗавершающегоЭтапа,
| ЗаказыОстатки.ЕдиницаИзмерения КАК ЕдиницаИзмерения1
|ИЗ
| (ВЫБРАТЬ
| ЭтапыПроизводства.Заказ КАК ЗаказНаПроизводство,
| ЭтапыПроизводства.Номенклатура КАК Номенклатура,
| ЭтапыПроизводства.Характеристика КАК Характеристика,
| СУММА(ЭтапыПроизводства.КоличествоПлан - ЭтапыПроизводства.КоличествоФакт) КАК КоличествоОстаток,
| ЭтапыПроизводства.Этап КАК Этап,
| ЭтапыПроизводства.Номенклатура.ЕдиницаИзмерения КАК ЕдиницаИзмерения,
| ЭтапыПроизводства.Спецификация КАК Спецификация,
| ЭтапыПроизводства.СтруктурнаяЕдиница КАК ПодразделениеЗавершающегоЭтапа
| ИЗ
| РегистрНакопления.ЭтапыПроизводства КАК ЭтапыПроизводства
| ГДЕ
| ЭтапыПроизводства.Заказ = &Ссылка
|
| СГРУППИРОВАТЬ ПО
| ЭтапыПроизводства.Номенклатура,
| ЭтапыПроизводства.Заказ,
| ЭтапыПроизводства.Характеристика,
| ЭтапыПроизводства.Этап,
| ЭтапыПроизводства.Спецификация,
| ЭтапыПроизводства.СтруктурнаяЕдиница,
| ЭтапыПроизводства.Номенклатура.ЕдиницаИзмерения) КАК ЗаказыОстатки
|
|СГРУППИРОВАТЬ ПО
| ЗаказыОстатки.ЗаказНаПроизводство,
| ЗаказыОстатки.Номенклатура,
| ЗаказыОстатки.Характеристика,
| ЗаказыОстатки.Этап,
| ЗаказыОстатки.Номенклатура.ТипНоменклатуры,
| ЗаказыОстатки.ЕдиницаИзмерения,
| ЗаказыОстатки.Спецификация,
| ЗаказыОстатки.ПодразделениеЗавершающегоЭтапа,
| ЗаказыОстатки.ЕдиницаИзмерения
|
|ИМЕЮЩИЕ
| СУММА(ЗаказыОстатки.КоличествоОстаток) > 0";
Запрос.Текст = Запрос.Текст;
Запрос.УстановитьПараметр("Ссылка", ДанныеЗаполнения);
МассивРезультатов = Запрос.Выполнить().Выгрузить();
Этот запрос формирует выборку, где каждая строка содержит уникальную комбинацию заказа, номенклатуры, характеристики и, что важно, Этапа. Если для одной и той же номенклатуры по заказу есть несколько этапов, они будут представлены отдельными строками. Это корректное поведение запроса, и именно с таким результатом мы будем работать далее, объединяя его программно.
В отличие от некоторых систем управления базами данных (СУБД), которые предоставляют специальные функции, такие как `STRING_AGG` или `GROUP_CONCAT`, для конкатенации строковых значений внутри группировки, язык запросов 1С не имеет прямой и универсальной агрегатной функции для объединения строк. Функции вроде `МАКСИМУМ()` или `МНИМУМ()` для строковых полей просто выберут одно строковое значение, но не объединят все. Поэтому для решения нашей задачи мы будем использовать программный подход после получения данных из запроса.
Наиболее гибким и распространенным способом решения этой задачи в 1С является программная обработка результата запроса. Мы будем итерировать по всем строкам, полученным из запроса, и динамически формировать строки нашей табличной части, объединяя значения поля "Этап".
Первым шагом нам необходимо убедиться, что реквизит в вашей табличной части, куда будут выводиться объединенные этапы, имеет подходящий тип данных.
Строка. Это наиболее простой вариант для отображения.СписокЗначений. В этом случае каждый этап будет элементом списка.В большинстве случаев для простого отображения достаточно типа Строка. Если же требуется интерактивность, выбирайте СписокЗначений. Если ваш реквизит изначально был справочником или другим ссылочным типом, его придется переделать в Строка или СписокЗначений.
После того как запрос выполнен и его результат выгружен (например, в МассивРезультатов или ТаблицуЗначений), мы переходим к программной обработке:
Продукция.Очистить();
ВыполненныеЭтапы.Очистить(); // Если 'ВыполненныеЭтапы' - это та ТЧ, куда хотим агрегировать
Для Каждого ВыборкаПродукция Из МассивРезультатов Цикл
// ...
КонецЦикла;
Для этого мы можем использовать методы поиска табличной части, например, НайтиСтроки(), но это может быть неэффективно для больших объемов. Более оптимальным будет пройтись по уже добавленным строкам целевой табличной части и сравнить значения ключевых полей. Или, что еще лучше, создать вспомогательную структуру (например, Соответствие), которая будет хранить ссылки на уже добавленные строки по их уникальным ключам (комбинациям "ЗаказНаПроизводство", "Номенклатура", "Характеристика").
Рассмотрим конкретный пример кода, который реализует описанный алгоритм. Предположим, что целевая табличная часть называется Продукция и в ней есть реквизит ЭтапыПроизводстваСтрока типа Строка, куда мы хотим объединить этапы.
Продукция.Очистить(); // Очищаем целевую табличную часть перед заполнением
// Вспомогательная структура для быстрого поиска уже добавленных строк.
// Ключ: Строка с уникальной комбинацией полей (Заказ, Номенклатура, Характеристика, Спецификация и т.д.)
// Значение: Ссылка на строку в табличной части Продукция
СоответствиеКлючейИСтрок = Новый Соответствие();
Для Каждого ВыборкаПродукция Из МассивРезультатов Цикл
// Формируем уникальный ключ для текущей строки, исключая Этап
КлючСтроки = Строка(ВыборкаПродукция.ЗаказНаПроизводство) + "#" +
Строка(ВыборкаПродукция.Номенклатура) + "#" +
Строка(ВыборкаПродукция.Характеристика) + "#" +
Строка(ВыборкаПродукция.Спецификация) + "#" +
Строка(ВыборкаПродукция.ПодразделениеЗавершающегоЭтапа);
// Проверяем, есть ли уже строка с таким ключом в нашей табличной части
СуществующаяСтрока = СоответствиеКлючейИСтрок.Получить(КлючСтроки);
Если СуществующаяСтрока = Неопределено Тогда
// Если строки нет, создаем новую
НоваяСтрока = ЭтотОбъект.Продукция.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, ВыборкаПродукция);
// Заполняем поле с этапами первым этапом
НоваяСтрока.ЭтапыПроизводстваСтрока = Строка(ВыборкаПродукция.Этап); // Предполагаем, что реквизит называется ЭтапыПроизводстваСтрока
// Добавляем новую строку в соответствие для быстрого поиска в будущем
СоответствиеКлючейИСтрок.Вставить(КлючСтроки, НоваяСтрока);
Иначе
// Если строка уже есть, добавляем текущий этап к существующей строке
// Используем Символы.ПС для переноса строки, чтобы этапы были каждый на новой строке
СуществующаяСтрока.ЭтапыПроизводстваСтрока = СуществующаяСтрока.ЭтапыПроизводстваСтрока + Символы.ПС + Строка(ВыборкаПродукция.Этап);
КонецЕсли;
// Если у вас есть связанная табличная часть, как в вашем коде (ВыполненныеЭтапы),
// и ее логика предполагает отдельное хранение этапов для каждой строки,
// то этот код должен быть отдельным и не касаться агрегации:
// НС = ВыполненныеЭтапы.Добавить();
// НС.КлючСвязи = НоваяСтрока.КлючСвязи; // Если НоваяСтрока.КлючСвязи - это уникальный идентификатор строки Продукции
// НС.Этап = ВыборкаПродукция.Этап;
КонецЦикла;
// Дополнительные действия, которые были в вашем коде
ЗаполнитьСкладыИРезервы();
Показать();
Пояснения к коду:
СоответствиеКлючейИСтрок. Это критически важно для производительности. Вместо того чтобы каждый раз пробегать по всей табличной части Продукция в поиске нужной строки, мы формируем уникальный ключ из полей, по которым хотим сгруппировать данные (все поля, кроме "Этап"), и используем его для быстрого доступа к уже добавленным строкам.СоответствиеКлючейИСтрок.Получить(КлючСтроки) позволяет нам мгновенно определить, была ли уже добавлена строка в табличную часть Продукция с таким же набором ключевых полей.СуществующаяСтрока = Неопределено), мы добавляем новую строку в Продукция, заполняем ее основными данными и устанавливаем первый этап. Затем эту новую строку добавляем в наше Соответствие.ЭтапыПроизводстваСтрока, используя Символы.ПС для создания многострочного текста. Вы можете использовать любой другой разделитель, например, ", " или "; ".Если вы выбрали для реквизита "Этап" тип СписокЗначений, код будет немного отличаться в части обработки поля "Этап":
// ... (начало кода такое же)
Если СуществующаяСтрока = Неопределено Тогда
НоваяСтрока = ЭтотОбъект.Продукция.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, ВыборкаПродукция);
// Создаем новый СписокЗначений и добавляем в него первый этап
НоваяСтрока.ЭтапыПроизводстваСписок = Новый СписокЗначений(); // Предполагаем, что реквизит называется ЭтапыПроизводстваСписок
НоваяСтрока.ЭтапыПроизводстваСписок.Добавить(ВыборкаПродукция.Этап, ВыборкаПродукция.Этап.Наименование); // Используем сам Этап как значение и его Наименование как представление
СоответствиеКлючейИСтрок.Вставить(КлючСтроки, НоваяСтрока);
Иначе
// Добавляем текущий этап в существующий СписокЗначений
СуществующаяСтрока.ЭтапыПроизводстваСписок.Добавить(ВыборкаПродукция.Этап, ВыборкаПродукция.Этап.Наименование);
КонецЕсли;
// ... (конец кода такой же)
Использование СписокЗначений дает больше возможностей для интерактивности, но может потребовать дополнительной настройки формы для его адекватного отображения (например, через динамические элементы формы, которые позволяют отображать элементы списка). Для простого вывода в ячейку табличного поля, скорее всего, придется конвертировать его в строку для отображения, что делает вариант с Строка более прямым.
Для того чтобы объединенная строка с этапами, разделенными Символы.ПС, корректно отображалась в табличной части на форме в несколько строк, необходимо убедиться, что поле формы, соответствующее реквизиту табличной части, настроено соответствующим образом:
Истина.Вы упомянули метод МассивРезультатов.Свернуть(). Этот метод применим к объекту ТаблицаЗначений. Его можно использовать для группировки данных и агрегации, но он имеет свои ограничения для строковых полей:
Метод Свернуть() позволяет указать поля группировки и поля для суммирования. Для числовых полей он выполнит стандартную агрегацию (сумма, количество, максимум, минимум). Однако для строковых полей он не предоставляет прямого механизма конкатенации. Если вы укажете строковое поле в качестве поля для "суммирования", Свернуть() просто выберет одно из значений (обычно первое или последнее в рамках группы, в зависимости от внутренней реализации), а не объединит их.
Поэтому, если вы решите использовать Свернуть(), вам придется применить двухшаговый подход:
Свернуть() по всем полям, кроме "Этап", чтобы получить уникальные строки.Этот подход усложняет логику, делая его менее прямым, чем описанный выше способ с использованием Соответствие и итерации по результату запроса. В большинстве случаев для строковой агрегации предпочтительнее прямой программный цикл.
Соответствие значительно улучшает производительность по сравнению с многократным поиском в табличной части.Строка и СписокЗначений. Если интерактивность не требуется, Строка будет проще в реализации и отображении. Если вам нужно работать с каждым этапом отдельно (например, открывать формы элементов справочника), то СписокЗначений — ваш выбор, но он потребует более сложной обработки на форме.Объединение нескольких значений из запроса в одну ячейку табличной части в 1С — это распространенная задача, которая эффективно решается программным путем. Используя итерацию по результату запроса и вспомогательное Соответствие для быстрого поиска, мы можем элегантно сгруппировать данные и представить их в удобном для пользователя виде. Этот подход является гибким, контролируемым и хорошо себя зарекомендовал в практике разработки на платформе 1С:Предприятие.