Как работать с таблицей значений в 1С 8.3: приёмы разработчика
Содержание
Таблица значений (ТаблицаЗначений) — универсальная коллекция в памяти: строки и колонки произвольных типов, которую можно заполнить, отфильтровать, отсортировать, посчитать по ней итоги и выгрузить в массив или табличную часть. Живёт только во время работы кода, в базе не хранится.
Обрабатывают таблицу значений обычно на сервере: в обработках, отчётах на СКД, при переносе данных и групповых операциях. Она хороша тем, что собирает разнородные данные в одном месте и позволяет крутить их как угодно без обращения к базе на каждый чих.
Ниже — рабочие приёмы для 1С 8.3: от создания колонок и заполнения из запроса до поиска, сортировки, свёртки, объединения таблиц и ускорения поиска индексами. Все примеры — на встроенном языке, их можно вставить в модуль и запустить. Пути и интерфейс — для платформы 8.3 с формой «Такси».
Создаём таблицу и описываем колонки
Сначала создаём пустой объект, затем добавляем колонки. У каждой колонки задают имя, тип и при желании заголовок для вывода:
ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
ТЗ.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 3)));
ТЗ.Колонки.Добавить("Сумма", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)), "Сумма, руб.");
Тип колонки указывать не обязательно — без ОписаниеТипов колонка примет любое значение. Но типизированные колонки защищают от мусора и быстрее работают при поиске и сортировке.
Описание типов можно собрать из нескольких типов сразу — тогда в колонку попадут значения любого из них:
ТЗ.Колонки.Добавить("Объект", Новый ОписаниеТипов("СправочникСсылка.Контрагенты, СправочникСсылка.Номенклатура"));
К колонке обращаются по имени (ТЗ.Колонки.Номенклатура) или по индексу (ТЗ.Колонки[0]). Удалить колонку — ТЗ.Колонки.Удалить("Сумма"), переименовать — через свойство Имя нужной колонки.
Имя колонки — это идентификатор во встроенном языке: только латиница и кириллица, цифры и подчёркивание, без пробелов и знаков. Если в имени нужен пробел или спецсимвол — это уже заголовок (третий параметр Добавить), а не имя. Обращение к несуществующей колонке по имени вызывает ошибку, поэтому имена задавайте один раз и используйте именно их.
Добавляем строки и заполняем данными
Новая строка создаётся методом Добавить() и возвращается как объект, которому присваивают значения колонок:
Строка = ТЗ.Добавить();
Строка.Номенклатура = ВыбраннаяНоменклатура;
Строка.Количество = 10;
Строка.Сумма = 2500;
Если нужно вставить строку в конкретную позицию, а не в конец, используйте Вставить(Индекс).
Когда данные приходят из другого объекта — структуры, строки запроса, другой таблицы — удобно заполнять свойства одной командой. ЗаполнитьЗначенияСвойств копирует значения по совпадающим именам:
ЗаполнитьЗначенияСвойств(Строка, ДанныеИсточника);
Чаще всего таблицу значений не наполняют вручную, а получают из запроса. Метод Выгрузить() результата запроса сразу отдаёт готовую таблицу с нужными колонками:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Продажи.Номенклатура КАК Номенклатура,
| Продажи.Количество КАК Количество,
| Продажи.Сумма КАК Сумма
|ИЗ
| РегистрНакопления.Продажи.Обороты КАК Продажи";
ТЗ = Запрос.Выполнить().Выгрузить();
Обход строк — циклом Для Каждого:
Для Каждого Строка Из ТЗ Цикл
Сообщить(Строка.Номенклатура.Наименование + ": " + Строка.Сумма);
КонецЦикла;
Внутри цикла Для Каждого нельзя удалять строки из той же таблицы — это обрывает обход с ошибкой. Для удаления по условию используйте обратный цикл по индексу (см. раздел об ошибках).
Ищем и отбираем нужные строки
Для поиска есть два метода, и путать их — частая причина багов.
Найти(Значение, "Колонки") возвращает первую подходящую строку или Неопределено, если ничего не нашлось:
НайденнаяСтрока = ТЗ.Найти(Товар, "Номенклатура");
Если НайденнаяСтрока <> Неопределено Тогда
НайденнаяСтрока.Количество = НайденнаяСтрока.Количество + 1;
КонецЕсли;
НайтиСтроки(Отбор) возвращает массив всех строк, подходящих под отбор. Отбор задают структурой, где ключи — имена колонок:
Отбор = Новый Структура("Номенклатура, Склад", Товар, Склад);
Строки = ТЗ.НайтиСтроки(Отбор);
Для Каждого Строка Из Строки Цикл
Строка.Количество = 0;
КонецЦикла;
Разница по сути такая:
| Метод | Что возвращает | Когда применять |
|---|---|---|
Найти | одну строку или Неопределено | нужна первая/единственная запись |
НайтиСтроки | массив строк (может быть пустым) | нужны все записи по условию |
НайтиСтроки ищет по точному совпадению и с учётом типа. Если колонка хранит число, а в отборе передана строка — строка не найдётся. Это самая частая причина «пустого» результата при визуально совпадающих данных.
Для диапазонов и сложных условий (больше, меньше, вхождение в список) готового метода нет — перебирайте строки циклом и проверяйте условие вручную либо получайте нужный срез сразу в запросе.
Сортируем строки в нужном порядке
Метод Сортировать принимает строку с именами колонок и направлением: Возр (по возрастанию, по умолчанию) или Убыв (по убыванию). Колонки перечисляют через запятую — сортировка идёт последовательно:
ТЗ.Сортировать("Сумма Убыв, Номенклатура Возр");
Сначала строки упорядочатся по убыванию суммы, а внутри одинаковых сумм — по наименованию номенклатуры.
Для ссылочных колонок (справочники, документы) сортировка идёт по внутреннему порядку ссылок, а не по наименованию. Если нужен порядок именно по названию — добавьте в таблицу отдельную строковую колонку с наименованием и сортируйте по ней, либо сразу упорядочивайте данные в запросе через УПОРЯДОЧИТЬ ПО.
Считаем итоги, группируем и сворачиваем
Сумму по числовой колонке даёт метод Итог:
ВсегоСумма = ТЗ.Итог("Сумма");
ВсегоКоличество = ТЗ.Итог("Количество");
Для группировки с суммированием есть Свернуть. Первый параметр — колонки группировки, второй — колонки, по которым суммируем. Все остальные колонки из таблицы удаляются:
// было: много строк по одной номенклатуре на разных складах
// станет: одна строка на номенклатуру с суммой количества и суммы
ТЗ.Свернуть("Номенклатура", "Количество, Сумма");
Можно группировать сразу по нескольким колонкам:
ТЗ.Свернуть("Номенклатура, Склад", "Количество, Сумма");
Свернуть меняет исходную таблицу и выбрасывает все колонки, которых нет ни в группировке, ни в суммировании. Если эти колонки ещё нужны — сверните копию (ТЗ.Скопировать()), а оригинал оставьте нетронутым.
Среднее, максимум и минимум метода «из коробки» не имеют — их считают перебором строк. Среднее аккуратно получить из готового итога и количества строк, а максимум — одним проходом:
Среднее = ?(ТЗ.Количество() > 0, ТЗ.Итог("Сумма") / ТЗ.Количество(), 0);
Максимум = 0;
Для Каждого Строка Из ТЗ Цикл
Максимум = Макс(Максимум, Строка.Сумма);
КонецЦикла;
Если такие расчёты нужны регулярно и по многим колонкам, дешевле получить агрегаты сразу в запросе функциями СУММА, СРЕДНЕЕ, МАКСИМУМ, МИНИМУМ и выгрузить готовый результат.
Преобразуем: массив, табличная часть, копия
Таблица значений легко конвертируется в соседние структуры данных.
Колонка в массив — ВыгрузитьКолонку. Удобно, когда нужен список значений, например чтобы передать его в параметр запроса В (&Список):
МассивНоменклатуры = ТЗ.ВыгрузитьКолонку("Номенклатура");
Обратная операция — ЗагрузитьКолонку(Массив, "Колонка") заполняет колонку из массива по порядку строк.
Копия таблицы — Скопировать(). Без параметров копирует всё; можно скопировать только часть строк и часть колонок:
КопияЦеликом = ТЗ.Скопировать();
Строки = ТЗ.НайтиСтроки(Новый Структура("Склад", ОсновнойСклад));
ВыборкаПоСкладу = ТЗ.Скопировать(Строки, "Номенклатура, Количество");
В табличную часть документа или справочника — метод Загрузить табличной части. Имена колонок таблицы должны совпадать с именами реквизитов табличной части:
Объект.Товары.Загрузить(ТЗ);
Обратно реквизиты выгружают так же просто: ТЗ = Объект.Товары.Выгрузить(). Можно выгрузить только часть колонок: Объект.Товары.Выгрузить("Номенклатура, Количество").
В файл или строку — для временного хранения или передачи таблицу сериализуют. ЗначениеВФайл(ПутьКФайлу, ТЗ) сохраняет её на диск, ЗначениеИзФайла(ПутьКФайлу) читает обратно. Для хранения в строке (например, в реквизите типа «Хранилище значения») подойдёт Новый ХранилищеЗначения(ТЗ).
Объединяем таблицы и выбираем подходящую коллекцию
Готового метода «слить две таблицы» в платформе нет. Строки из одной таблицы переносят в другую циклом, копируя значения по совпадающим колонкам:
Для Каждого СтрокаИсточника Из ТЗИсточник Цикл
НоваяСтрока = ТЗПриёмник.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаИсточника);
КонецЦикла;
ЗаполнитьЗначенияСвойств копирует только те колонки, имена которых есть в обеих таблицах, остальные молча пропускает. Если структура колонок совпадает полностью, после переноса можно свернуть результат и получить сводные данные из двух источников.
Чтобы объединить строки по ключу (как ЛЕВОЕ СОЕДИНЕНИЕ в запросе), в приёмнике ищите строку методом Найти по колонке-ключу: нашлась — дополняйте её значениями, не нашлась — добавляйте новую. Для сотен тысяч строк такой поиск уже заметно тормозит — тогда соединяйте данные в запросе, а не в памяти.
Таблица значений — не единственная коллекция. Под разные задачи удобнее свои типы:
| Коллекция | Когда применять |
|---|---|
ТаблицаЗначений | много строк с несколькими колонками, нужны отбор, сортировка, итоги |
Соответствие | быстрый доступ по ключу (кэш «ключ → значение»), уникальные ключи |
СписокЗначений | список с пометками и представлениями для выбора пользователем |
Массив | простой перечень значений без колонок и ключей |
ДеревоЗначений | иерархия со вложенными строками (группы и подчинённые) |
Если по данным нужен только поиск по одному ключу — Соответствие быстрее таблицы значений и не требует индексов. Таблицу берите там, где есть несколько колонок и нужны групповые операции.
Ускоряем работу: индексы и частые ошибки
На больших таблицах узкое место — поиск. Каждый вызов НайтиСтроки без индекса перебирает все строки. Если ищете многократно по одним и тем же колонкам, добавьте индекс:
ТЗ.Индексы.Добавить("Номенклатура, Склад");
После этого НайтиСтроки и Найти по этим колонкам работают на порядок быстрее. Набор колонок в отборе должен совпадать с индексом (или начинаться с его левых колонок) — иначе индекс не применится.
Ещё несколько приёмов, которые экономят время и память:
- 🗑 Удаление строк по условию — только обратным циклом по индексу, иначе собьются номера строк:
Для Индекс = ТЗ.Количество() - 1 По 0 Цикл
Если ТЗ[Индекс].Количество = 0 Тогда
ТЗ.Удалить(Индекс);
КонецЕсли;
КонецЦикла;
- ⚙️ Обработка на сервере. Таблицу значений можно передавать между клиентом и сервером, но это сериализация всего объёма. Большие таблицы собирайте и обрабатывайте в серверных методах (
&НаСервере), а на клиент возвращайте только итог. - 📊 Вывод пользователю. Напрямую на управляемую форму таблицу значений не положить — на форме используется реквизит с типом «ТаблицаЗначений» (
ДанныеФормыКоллекция). Для разовой выдачи проще вывести таблицу в табличный документ или в сообщение.
Диагностика типичных сбоев при обработке таблицы:
Частые вопросы
Чем `Найти` отличается от `НайтиСтроки`?
Найти возвращает одну первую строку или Неопределено. НайтиСтроки возвращает массив всех подходящих строк (возможно, пустой). Для единственной записи берите Найти, для выборки — НайтиСтроки.Почему `НайтиСтроки` не находит строку, хотя значение видно в данных?
Как сохранить таблицу значений между сеансами?
ЗначениеВФайл, в реквизит типа «Хранилище значения» через Новый ХранилищеЗначения(ТЗ) или перенесите данные в табличную часть документа методом Загрузить.Можно ли передавать таблицу значений между клиентом и сервером?
Как получить таблицу значений из результата запроса?
Выгрузить() у результата: ТЗ = Запрос.Выполнить().Выгрузить(). Колонки и типы подставятся из полей запроса автоматически.