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