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