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