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

· 9 мин

Содержание

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

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

Чем таблица значений отличается от массива и структуры

Выбор коллекции зависит от формы данных. Если вам нужен просто список — хватит массива. Если набор именованных полей одной записи — структура. Таблица значений нужна, когда записей много и у каждой одинаковый набор полей.

КоллекцияЧто хранитКогда брать
МассивОдномерный список значенийПеречень без полей: список ссылок, строк
СтруктураПары «ключ — значение», ключ = имяОдна запись с именованными полями
СоответствиеПары «ключ — значение», ключ любойБыстрый доступ по произвольному ключу
СписокЗначенийЗначения + представление + пометкаВыбор пользователя, меню
ТаблицаЗначенийСтроки × типизированные колонкиНабор однотипных записей: поиск, сортировка, итоги

Главное отличие таблицы — она двумерная. У неё есть коллекция колонок (Колонки) и коллекция строк. К значению обращаются по строке и имени колонки: Строка.Сумма. Встроенные методы поиска, сортировки и свёртки экономят десятки строк ручного кода.

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

Для иерархии (строки внутри строк) есть отдельный объект — ДеревоЗначений. Если вложенность не нужна, берите плоскую таблицу: она проще и быстрее.

Как создать таблицу и описать её колонки

Колонка без явного типа принимает значения любого типа. Чтобы 1С контролировала данные и правильно считала итоги, тип лучше задавать через ОписаниеТипов.

Товары = Новый ТаблицаЗначений;

// Колонка без типа — примет что угодно
Товары.Колонки.Добавить("Номенклатура");

// Колонка с типом «Число», 15 знаков, 2 после запятой
ОписаниеЧисла = Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2));
Товары.Колонки.Добавить("Количество", ОписаниеЧисла, "Кол-во");
Товары.Колонки.Добавить("Цена", ОписаниеЧисла);
Товары.Колонки.Добавить("Сумма", ОписаниеЧисла, "Сумма, руб");

У метода Добавить колонки три полезных параметра:

  • 🏷️ имя — по нему обращаются в коде, латиница или кириллица без пробелов;
  • 📐 тип — объект ОписаниеТипов, можно перечислить несколько типов через запятую в строке;
  • 📝 заголовок — то, что увидит пользователь, если таблица выводится на форму.

Проверить состав колонок можно обращением по имени или индексу: Товары.Колонки.Количество(), Товары.Колонки[0].Имя, Товары.Колонки.Найти("Цена"). Если колонки с таким именем нет, Найти вернёт Неопределено — это удобно для проверки перед добавлением.

В одной колонке можно разрешить несколько типов — перечислите их в первом параметре ОписаниеТипов через запятую:

ОписаниеСсылки = Новый ОписаниеТипов("СправочникСсылка.Номенклатура, СправочникСсылка.Услуги");
Строки.Колонки.Добавить("Объект", ОписаниеСсылки);

На практике таблицу редко собирают колонку за колонкой вручную. Чаще её получают готовой из результата запроса — метод Выгрузить возвращает заполненную ТаблицаЗначений с колонками по именам полей выборки:

Запрос = Новый Запрос("ВЫБРАТЬ Номенклатура, Количество, Сумма ИЗ ...");
Товары = Запрос.Выполнить().Выгрузить();

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

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

Новая строка создаётся методом Добавить (в конец) или Вставить (по индексу). Оба возвращают объект строки, которому тут же присваивают значения колонок.

НоваяСтрока = Товары.Добавить();
НоваяСтрока.Номенклатура = "Болт М8";
НоваяСтрока.Количество = 100;
НоваяСтрока.Цена = 12.5;
НоваяСтрока.Сумма = НоваяСтрока.Количество * НоваяСтрока.Цена; // 1250

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

ЗаполнитьЗначенияСвойств(НоваяСтрока, ДанныеИзЗапроса);

Чтобы добавить строку не в конец, а в нужную позицию, используйте Вставить(Индекс) — остальные строки сдвинутся вниз. Переставить уже существующую строку помогает Сдвинуть(Строка, Смещение): положительное смещение двигает строку вниз, отрицательное — вверх.

ПерваяСтрока = Товары.Вставить(0);   // вставить в начало таблицы
ПерваяСтрока.Номенклатура = "Итоговая строка";
Товары.Сдвинуть(ПерваяСтрока, 2);     // опустить её на 2 позиции вниз

Перебор строк — через Для Каждого. Внутри цикла значения доступны по имени колонки:

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

Не удаляйте строки внутри цикла Для Каждого по той же таблице — это нарушает перебор. Безопасный приём: сначала собрать ненужные строки методом НайтиСтроки, потом удалить их отдельным циклом.

НаУдаление = Товары.НайтиСтроки(Новый Структура("Количество", 0));
Для Каждого Строка Из НаУдаление Цикл
    Товары.Удалить(Строка);
КонецЦикла;

Полностью стереть содержимое, оставив колонки, — метод Очистить(). Узнать номер строки в таблице — Товары.Индекс(Строка), получить строку по номеру — Товары[0].

Поиск, отбор и сортировка данных

Для поиска есть два разных метода, их часто путают.

Найти(Значение, "Колонки") ищет одно значение в указанных колонках и возвращает первую подходящую строку или Неопределено. Проверяйте результат перед обращением к полям:

Строка = Товары.Найти("Болт М8", "Номенклатура");
Если Строка <> Неопределено Тогда
    Строка.Количество = Строка.Количество + 50;
КонецЕсли;

НайтиСтроки(Отбор) возвращает массив всех строк, подходящих под отбор. Отбор задаётся структурой, где ключ — имя колонки, значение — искомое:

Отбор = Новый Структура;
Отбор.Вставить("Номенклатура", "Болт М8");
Отбор.Вставить("Цена", 12.5);
Строки = Товары.НайтиСтроки(Отбор); // массив строк, может быть пустым

Сортировка выполняется методом Сортировать. В строке перечисляют колонки с направлением: Возр (по возрастанию, по умолчанию) или Убыв.

Товары.Сортировать("Сумма Убыв, Номенклатура Возр");

Если НайтиСтроки вызывается в цикле по большой таблице, добавьте индекс: Товары.Индексы.Добавить("Номенклатура"). Поиск по проиндексированной колонке идёт заметно быстрее линейного перебора.

Итоги, группировка и свёртка строк

Чтобы не считать сумму колонки вручную циклом, есть метод Итог:

ОбщаяСумма = Товары.Итог("Сумма"); // сумма всех значений колонки

Группировку с суммированием делает Свернуть. Первый параметр — колонки группировки, второй — суммируемые числовые колонки. После свёртки остаётся по одной строке на каждое сочетание значений в колонках группировки:

// Схлопнуть до строк по номенклатуре, просуммировав количество и сумму
Товары.Свернуть("Номенклатура", "Количество, Сумма");

Разберём на примере. Пусть в таблице по три строки на каждый из двух складов. После Товары.Свернуть("Склад", "Сумма") останется две строки — по одной на склад, и в колонке Сумма будет итог по складу. Колонки, не попавшие ни в группировку, ни в суммирование (например, Цена), из результата исчезнут — Свернуть оставляет только перечисленные. Если нужно их сохранить, скопируйте таблицу до свёртки или не включайте такие колонки в исходный набор.

Частый сценарий — сумма по отбору: отобрать строки одного склада и сложить их. Здесь выручает комбинация Скопировать и Итог:

ПоСкладу = Товары.Скопировать(Новый Структура("Склад", ВыбранныйСклад));
СуммаПоСкладу = ПоСкладу.Итог("Сумма");

Если нужно выгрузить все значения одной колонки в массив (например, собрать ссылки, чтобы передать их параметром в другой запрос) — используйте ВыгрузитьКолонку, а записать массив обратно в колонку — ЗагрузитьКолонку. Порядок значений в массиве соответствует порядку строк:

МассивНоменклатуры = Товары.ВыгрузитьКолонку("Номенклатура");

// ...получили цены отдельно и кладём обратно в том же порядке строк
Товары.ЗагрузитьКолонку(МассивЦен, "Цена");

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

Скопировать часть таблицы по отбору помогает Скопировать. Можно задать и отбор, и состав колонок результата:

ТолькоДорогие = Товары.Скопировать(Новый Структура("Цена", 1000), "Номенклатура, Цена");

Таблица значений на управляемой форме

На управляемой форме (интерфейс «Такси») таблица отображается через реквизит формы типа ТаблицаЗначений, к которому привязан элемент-таблица. На клиенте вы работаете не с самим объектом, а с данными формы.

Порядок такой:

  1. В конфигураторе добавьте реквизит формы типа ТаблицаЗначений и опишите его колонки.
  2. Перетащите реквизит в дерево элементов формы — появится таблица на экране.
  3. В модуле формы обрабатывайте данные — на сервере через настоящий объект, на клиенте через данные формы.

Чтобы получить из реквизита формы полноценный объект ТаблицаЗначений и обработать его серверными методами, применяют пару методов формы:

&НаСервере
Процедура ПересчитатьНаСервере()
    ТЗ = РеквизитФормыВЗначение("Товары");  // реквизит формы -> ТаблицаЗначений
    ТЗ.Сортировать("Сумма Убыв");
    ТЗ.Свернуть("Номенклатура", "Сумма");
    ЗначениеВРеквизитФормы(ТЗ, "Товары");    // обратно в реквизит формы
КонецПроцедуры

Напрямую передать ТаблицаЗначений параметром в клиентскую процедуру нельзя — на тонком клиенте этого типа нет. Для передачи с сервера на клиент используйте реквизит формы или временное хранилище (ПоместитьВоВременноеХранилище), а на клиенте оперируйте строками данных формы.

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

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

Частые ошибки и ускорение работы с таблицей

Самые частые ошибки связаны с обращением к несуществующим колонкам и с результатом поиска Неопределено. Разбор по симптому:

Диагностика: выберите симптом

Что ускоряет код при больших объёмах:

  • 🚀 Индексы. Перед множественными НайтиСтроки добавьте Индексы.Добавить("Колонка") — поиск пойдёт по индексу, а не перебором.
  • 🧮 Свернуть вместо ручной группировки. Один вызов Свернуть быстрее цикла с накоплением во вспомогательной таблице.
  • 📤 ВыгрузитьКолонку вместо цикла, когда нужен только массив значений одной колонки.
  • 🗄️ Запрос вместо таблицы в памяти. Если данных десятки тысяч строк и их можно отобрать и сгруппировать запросом к базе — делайте это на стороне СУБД, а в таблицу значений выгружайте уже готовый результат.
  • 🔁 Не меняйте число строк в прямом переборе. Удаление и вставку внутри Для Каждого по той же таблице выносите в отдельный цикл по массиву из НайтиСтроки.

Ещё одна тонкость: колонка без указанного типа хранит значения любого типа, и Итог по ней отработает только если там действительно числа. Задавайте тип колонки сразу — это и защита от мусора, и подсказка платформе при расчётах.

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

И про отладку: чтобы посмотреть содержимое таблицы в точке останова, в отладчике откройте значение переменной — таблица показывается с колонками и строками, как на форме. Это быстрее, чем выводить строки в сообщения вручную.

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

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