Работа с табличной частью объекта во встроенном языке 1С

· 9 мин

Содержание

Обратиться к табличной части объекта можно по её имени как к свойству: Документ.Товары. Это коллекция строк, индексация идёт с нуля, первая строка — Документ.Товары[0], число строк даёт Документ.Товары.Количество().

Дальше вы читаете и меняете значения колонок через точку (Строка.Цена), перебираете строки циклом, ищете нужные методами Найти и НайтиСтроки, добавляете и удаляете их. В управляемой форме добавляется ещё один слой — текущая строка таблицы и данные формы. Разберём каждый приём с рабочими примерами на коде.

Для примеров возьмём документ с табличной частью «Товары» и колонками Номенклатура, Количество, Цена, Сумма.

Как устроена табличная часть объекта

Табличная часть — это упорядоченная коллекция однотипных строк. У каждой строки один и тот же набор колонок, заданных в конфигураторе. Тип значения в контексте модуля — ТабличнаяЧастьОбъекта.<Имя>, строка имеет тип СтрокаТабличнойЧастиОбъекта.

Ключевые свойства коллекции:

  • 🔢 строки пронумерованы с нуля до Количество() - 1;
  • 📦 к колонке строки обращаемся через точку: Строка.Цена;
  • 🧩 порядок строк сохраняется, пока вы его не измените сортировкой или вставкой;
  • ⚙️ у коллекции есть собственные методы — Найти, НайтиСтроки, Итог, Выгрузить, Свернуть и другие.

Прочитать табличную часть по ссылке, не загружая объект целиком, можно запросом к вложенной таблице Документ.ПоступлениеТоваров.Товары:

Запрос = Новый Запрос;
Запрос.Текст =
    "ВЫБРАТЬ Номенклатура, Количество, Сумма
    |ИЗ Документ.ПоступлениеТоваров.Товары
    |ГДЕ Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", СсылкаНаДокумент);
Выборка = Запрос.Выполнить().Выбрать();

Так вы читаете строки без выгрузки всего документа в память — это быстрее для отчётов и проверок. Но если вы уже в модуле объекта или прочитали объект через Ссылка.ПолучитьОбъект() и собираетесь менять данные, работайте напрямую с коллекцией — это проще и нагляднее.

Имя переменной под табличную часть сокращает код. Строка Товары = Объект.Товары; один раз — и дальше обращаетесь к Товары, а не к длинному Объект.Товары в каждой строке.

Доступ к строке по индексу и к значению колонки

Строку берут по индексу в квадратных скобках или методом Получить:

ПерваяСтрока = Объект.Товары[0];
ТретьяСтрока = Объект.Товары.Получить(2);  // тот же индекс 2

Оба варианта равнозначны. [0] короче, Получить(0) читается чуть яснее в сложных выражениях. Индексация начинается с нуля, поэтому у последней строки индекс Количество() - 1, а не Количество(). Обращение по индексу Количество() выдаст ошибку «Значение индекса выходит за пределы диапазона».

Прочитать и записать значение колонки:

Строка = Объект.Товары[0];
Цена = Строка.Цена;                    // чтение
Строка.Сумма = Строка.Количество * Строка.Цена;  // запись

Узнать индекс конкретной строки, если она у вас уже на руках, помогает метод Индекс:

Поз = Объект.Товары.Индекс(Строка);

Он удобен, когда строка получена из другого места (например, из текущих данных формы), а вам нужен её номер для вставки или удаления соседней строки.

Перебор строк циклом

Для прохода по всем строкам есть два способа. Когда порядок и индекс не важны, берите Для Каждого — он короче и читается лучше:

ИтогоПоДокументу = 0;
Для Каждого СтрокаТовара Из Объект.Товары Цикл
    СтрокаТовара.Сумма = СтрокаТовара.Количество * СтрокаТовара.Цена;
    ИтогоПоДокументу = ИтогоПоДокументу + СтрокаТовара.Сумма;
КонецЦикла;

Когда нужен сам индекс — например, чтобы сравнить строку с предыдущей или обратиться к соседней, — используйте цикл со счётчиком:

Для Сч = 0 По Объект.Товары.Количество() - 1 Цикл
    Строка = Объект.Товары[Сч];
    Если Сч > 0 Тогда
        ПредыдущаяСтрока = Объект.Товары[Сч - 1];
        // сравнение с предыдущей строкой
    КонецЕсли;
КонецЦикла;

Внутри цикла Для Каждого нельзя добавлять или удалять строки той же коллекции — это ломает перебор и приводит к пропуску строк или ошибке. Для изменения состава строк берите цикл со счётчиком и идите в обратном порядке (подробнее — в разделе про удаление).

Сумму по колонке не обязательно считать вручную в цикле. Готовый итог даёт метод Итог:

ОбщаяСумма = Объект.Товары.Итог("Сумма");

Он складывает значения числовой колонки по всем строкам за один вызов — быстрее и короче ручного накопления.

Поиск и отбор строк в коллекции

Для поиска одной строки по значению колонки есть Найти. Он возвращает первую подходящую строку или Неопределено, если совпадений нет:

НайденнаяСтрока = Объект.Товары.Найти(ВыбраннаяНоменклатура, "Номенклатура");
Если НайденнаяСтрока <> Неопределено Тогда
    НайденнаяСтрока.Количество = НайденнаяСтрока.Количество + 1;
Иначе
    НоваяСтрока = Объект.Товары.Добавить();
    НоваяСтрока.Номенклатура = ВыбраннаяНоменклатура;
    НоваяСтрока.Количество = 1;
КонецЕсли;

Это типичный приём «нашли — увеличили, не нашли — добавили». Обязательно проверяйте результат на Неопределено перед обращением к колонкам, иначе получите ошибку при пустом поиске.

Когда нужны все строки по условию, а не одна, берите НайтиСтроки. Он принимает структуру отбора «колонка → значение» и возвращает массив строк:

Отбор = Новый Структура("Номенклатура", ВыбраннаяНоменклатура);
НайденныеСтроки = Объект.Товары.НайтиСтроки(Отбор);
Для Каждого Строка Из НайденныеСтроки Цикл
    Строка.Цена = НоваяЦена;
КонецЦикла;

Важная деталь: массив содержит ссылки на те же строки табличной части, а не их копии. Меняете значение в элементе массива — меняется строка в документе. В отбор можно положить сразу несколько колонок — тогда вернутся строки, где совпали все условия одновременно.

Если нужна только проверка «есть ли такая строка», НайтиСтроки вернёт пустой массив при отсутствии совпадений — проверяйте Количество() результата.

Добавление, вставка и удаление строк

Новую строку в конец добавляет Добавить — он сразу возвращает созданную строку:

НоваяСтрока = Объект.Товары.Добавить();
НоваяСтрока.Номенклатура = Товар;
НоваяСтрока.Количество = 10;
НоваяСтрока.Цена = 150;
НоваяСтрока.Сумма = НоваяСтрока.Количество * НоваяСтрока.Цена;

Чтобы вставить строку на конкретную позицию, а не в конец, используйте Вставить с индексом:

СтрокаВНачало = Объект.Товары.Вставить(0);  // станет первой

Удаление строки — метод Удалить. Ему передают либо саму строку, либо её индекс:

Объект.Товары.Удалить(Строка);   // по ссылке на строку
Объект.Товары.Удалить(0);        // по индексу

Удалить все строки сразу — Очистить().

Главная ловушка — удаление в цикле. При удалении строки все следующие сдвигают индекс на единицу. Если идти вперёд обычным циклом и удалять, часть строк проскочит мимо проверки. Удаляйте строки, двигаясь по коллекции в обратном порядке — от последней к первой:

Для Сч = Объект.Товары.Количество() - 1 По 0 Цикл
    Если Объект.Товары[Сч].Количество = 0 Тогда
        Объект.Товары.Удалить(Сч);
    КонецЕсли;
КонецЦикла;

При обратном проходе сдвиг индексов не мешает: удалённые строки находятся выше уже пройденных.

Табличная часть в управляемой форме

В управляемой форме у табличной части два разных контекста, и их путают чаще всего.

На сервере работайте с данными объекта формы как с обычной табличной частью — все методы доступны:

&НаСервере
Процедура ПересчитатьСуммыНаСервере()
    Для Каждого Строка Из Объект.Товары Цикл
        Строка.Сумма = Строка.Количество * Строка.Цена;
    КонецЦикла;
КонецПроцедуры

На клиенте к коллекции Объект.Товары тоже можно обращаться — перебирать циклом, читать и менять значения, добавлять и удалять строки. Но набор методов у данных формы меньше, чем у табличной части объекта: привычные Найти, НайтиСтроки, Итог и Выгрузить на клиентской коллекции формы недоступны. Для поиска по условию на клиенте перебирайте строки циклом, а сложную обработку выносите на сервер, где у объекта есть все методы.

Отдельная задача — текущая строка таблицы, на которой стоит курсор. Её дают не данные объекта, а элемент формы:

&НаКлиенте
Процедура УстановитьЦенуВТекущейСтроке()
    ТекущаяСтрока = Элементы.Товары.ТекущиеДанные;
    Если ТекущаяСтрока <> Неопределено Тогда
        ТекущаяСтрока.Цена = 0;
    КонецЕсли;
КонецПроцедуры

Элементы.Товары — это элемент формы (таблица), а ТекущиеДанные — строка под курсором или Неопределено, если таблица пуста или строка не выбрана. Проверка на Неопределено здесь обязательна: пользователь может вызвать команду, не выделив строку.

Если в серверной процедуре нужны полноценные методы поиска по табличной части формы, получите объект целиком: ОбъектЗначение = РеквизитФормыВЗначение("Объект");. У ОбъектЗначение.Товары снова доступны Найти, НайтиСтроки и Итог.

Обмен с таблицей значений: Выгрузить и Загрузить

Когда над строками нужно провести массовую обработку — сгруппировать, отсортировать по нескольким ключам, построить индекс для быстрого поиска, — удобно выгрузить табличную часть в отдельную таблицу значений, поработать с ней и при необходимости залить обратно.

Выгрузка всех строк и всех колонок:

ТаблицаТоваров = Объект.Товары.Выгрузить();

Результат — самостоятельная ТаблицаЗначений. Это копия: правки в ней не трогают документ, пока вы не загрузите данные назад. У таблицы значений свой богатый набор методов — Свернуть, НайтиСтроки, Сортировать, индексы, — которыми удобно пользоваться вне объекта.

Обратная операция — метод Загрузить. Он заменяет все строки табличной части строками из таблицы значений, сопоставляя колонки по именам:

Объект.Товары.Загрузить(ТаблицаТоваров);

Загрузить полностью замещает содержимое табличной части: прежние строки удаляются, остаются только строки из переданной таблицы. Если нужно изменить лишь часть строк, правьте их на месте, а не выгружайте и грузите весь состав.

Этот приём экономит обращения к объекту: вместо десятков правок строк в цикле вы один раз выгрузили, обработали в памяти и один раз загрузили обратно. На больших документах разница в скорости заметна.

Производительность и частые ошибки

На больших табличных частях поиск и перебор стоят дорого. Несколько приёмов, которые заметно ускоряют код:

  • 🚀 Не вызывайте Количество() в условии цикла каждый раз без нужды. В простом переборе это не критично, но внутри вложенных циклов лучше сохранить число в переменную.
  • 🚀 Выгружайте в таблицу значений для массовой обработки. ТаблицаТоваров = Объект.Товары.Выгрузить(); даёт ТаблицаЗначений, по которой удобно строить индексы и свёртки, не трогая объект.
  • 🚀 Сворачивайте дубли одним вызовом. Объект.Товары.СвернутьСтроки("Номенклатура", "Количество, Сумма"); объединит строки по номенклатуре и сложит количества и суммы — вместо ручного поиска и слияния.
  • 🚀 Заполняйте колонку целиком методом, а не циклом. Объект.Товары.ЗаполнитьЗначения(СтавкаНДС, "СтавкаНДС"); проставит одно значение во все строки за раз.
  • 🚀 Не ищите Найти внутри цикла по другой коллекции. Поиск в цикле даёт квадратичную сложность и резко тормозит на больших объёмах. Соберите нужные значения один раз в соответствие (Новый Соответствие) и обращайтесь к нему по ключу.
  • 🚀 Сортируйте встроенным методом: Объект.Товары.Сортировать("Номенклатура Возр, Сумма Убыв");.

Теперь ошибки, на которые уходит больше всего времени при отладке:

  1. Индекс с единицы вместо нуля. Первая строка — индекс 0, последняя — Количество() - 1. Обращение по Количество() даёт выход за границу диапазона.
  2. Нет проверки на Неопределено после Найти. Если совпадения нет, метод вернёт Неопределено, и обращение к колонке упадёт с ошибкой.
  3. Изменение коллекции внутри Для Каждого. Добавление или удаление строк во время такого перебора ломает обход. Меняйте состав строк циклом со счётчиком.
  4. Удаление вперёд по индексу. Из-за сдвига индексов часть строк проскакивает. Идите от последней строки к первой.
  5. Путаница клиент/сервер в форме. Найти и Выгрузить на клиентских данных формы недоступны — переносите такую логику на сервер.
Проверьте себя
Какой индекс у первой строки табличной части?
Что вернёт метод `Найти`, если совпадений нет?
В каком порядке безопасно удалять строки в цикле?

Частые вопросы

Чем `Найти` отличается от `НайтиСтроки`?
Найти возвращает одну первую подходящую строку или Неопределено. НайтиСтроки возвращает массив всех строк, удовлетворяющих структуре отбора; при отсутствии совпадений массив пустой.
Меняются ли строки документа, если я правлю элементы массива из `НайтиСтроки`?
Да. Массив содержит ссылки на те же строки табличной части, а не их копии. Изменение значения в элементе массива меняет строку в объекте.
Как получить сумму по колонке без цикла?
Методом Итог: Объект.Товары.Итог("Сумма"). Он складывает значения числовой колонки по всем строкам за один вызов.
Почему на клиенте в форме не работает `Найти` по табличной части?
У данных формы набор методов ограничен — поиска и выгрузки среди них нет. Перебирайте строки циклом на клиенте либо выполняйте поиск на сервере, где доступна полная табличная часть объекта.
Как обратиться к строке, на которой стоит курсор в таблице формы?
Через элемент формы: Элементы.Товары.ТекущиеДанные. Результат — строка под курсором или Неопределено, если ничего не выбрано.