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

· 9 мин

Содержание

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

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

Ниже — рабочие приёмы для 1С 8.3: от создания колонок и заполнения из запроса до поиска, сортировки, свёртки, объединения таблиц и ускорения поиска индексами. Все примеры — на встроенном языке, их можно вставить в модуль и запустить. Пути и интерфейс — для платформы 8.3 с формой «Такси».

Создаём таблицу и описываем колонки

Сначала создаём пустой объект, затем добавляем колонки. У каждой колонки задают имя, тип и при желании заголовок для вывода:

ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
ТЗ.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 3)));
ТЗ.Колонки.Добавить("Сумма", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)), "Сумма, руб.");

Тип колонки указывать не обязательно — без ОписаниеТипов колонка примет любое значение. Но типизированные колонки защищают от мусора и быстрее работают при поиске и сортировке.

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

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

К колонке обращаются по имени (ТЗ.Колонки.Номенклатура) или по индексу (ТЗ.Колонки[0]). Удалить колонку — ТЗ.Колонки.Удалить("Сумма"), переименовать — через свойство Имя нужной колонки.

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

Добавляем строки и заполняем данными

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

Строка = ТЗ.Добавить();
Строка.Номенклатура = ВыбраннаяНоменклатура;
Строка.Количество = 10;
Строка.Сумма = 2500;

Если нужно вставить строку в конкретную позицию, а не в конец, используйте Вставить(Индекс).

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

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

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

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

Обход строк — циклом Для Каждого:

Для Каждого Строка Из ТЗ Цикл
    Сообщить(Строка.Номенклатура.Наименование + ": " + Строка.Сумма);
КонецЦикла;

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

Ищем и отбираем нужные строки

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

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

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

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

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

Разница по сути такая:

МетодЧто возвращаетКогда применять
Найтиодну строку или Неопределенонужна первая/единственная запись
НайтиСтрокимассив строк (может быть пустым)нужны все записи по условию

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

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

Сортируем строки в нужном порядке

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

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

Сначала строки упорядочатся по убыванию суммы, а внутри одинаковых сумм — по наименованию номенклатуры.

Для ссылочных колонок (справочники, документы) сортировка идёт по внутреннему порядку ссылок, а не по наименованию. Если нужен порядок именно по названию — добавьте в таблицу отдельную строковую колонку с наименованием и сортируйте по ней, либо сразу упорядочивайте данные в запросе через УПОРЯДОЧИТЬ ПО.

Считаем итоги, группируем и сворачиваем

Сумму по числовой колонке даёт метод Итог:

ВсегоСумма = ТЗ.Итог("Сумма");
ВсегоКоличество = ТЗ.Итог("Количество");

Для группировки с суммированием есть Свернуть. Первый параметр — колонки группировки, второй — колонки, по которым суммируем. Все остальные колонки из таблицы удаляются:

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

Можно группировать сразу по нескольким колонкам:

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

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

Среднее, максимум и минимум метода «из коробки» не имеют — их считают перебором строк. Среднее аккуратно получить из готового итога и количества строк, а максимум — одним проходом:

Среднее = ?(ТЗ.Количество() > 0, ТЗ.Итог("Сумма") / ТЗ.Количество(), 0);

Максимум = 0;
Для Каждого Строка Из ТЗ Цикл
    Максимум = Макс(Максимум, Строка.Сумма);
КонецЦикла;

Если такие расчёты нужны регулярно и по многим колонкам, дешевле получить агрегаты сразу в запросе функциями СУММА, СРЕДНЕЕ, МАКСИМУМ, МИНИМУМ и выгрузить готовый результат.

Преобразуем: массив, табличная часть, копия

Таблица значений легко конвертируется в соседние структуры данных.

Колонка в массив — ВыгрузитьКолонку. Удобно, когда нужен список значений, например чтобы передать его в параметр запроса В (&Список):

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

Обратная операция — ЗагрузитьКолонку(Массив, "Колонка") заполняет колонку из массива по порядку строк.

Копия таблицы — Скопировать(). Без параметров копирует всё; можно скопировать только часть строк и часть колонок:

КопияЦеликом = ТЗ.Скопировать();
Строки = ТЗ.НайтиСтроки(Новый Структура("Склад", ОсновнойСклад));
ВыборкаПоСкладу = ТЗ.Скопировать(Строки, "Номенклатура, Количество");

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

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

Обратно реквизиты выгружают так же просто: ТЗ = Объект.Товары.Выгрузить(). Можно выгрузить только часть колонок: Объект.Товары.Выгрузить("Номенклатура, Количество").

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

Объединяем таблицы и выбираем подходящую коллекцию

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

Для Каждого СтрокаИсточника Из ТЗИсточник Цикл
    НоваяСтрока = ТЗПриёмник.Добавить();
    ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаИсточника);
КонецЦикла;

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

Чтобы объединить строки по ключу (как ЛЕВОЕ СОЕДИНЕНИЕ в запросе), в приёмнике ищите строку методом Найти по колонке-ключу: нашлась — дополняйте её значениями, не нашлась — добавляйте новую. Для сотен тысяч строк такой поиск уже заметно тормозит — тогда соединяйте данные в запросе, а не в памяти.

Таблица значений — не единственная коллекция. Под разные задачи удобнее свои типы:

КоллекцияКогда применять
ТаблицаЗначениймного строк с несколькими колонками, нужны отбор, сортировка, итоги
Соответствиебыстрый доступ по ключу (кэш «ключ → значение»), уникальные ключи
СписокЗначенийсписок с пометками и представлениями для выбора пользователем
Массивпростой перечень значений без колонок и ключей
ДеревоЗначенийиерархия со вложенными строками (группы и подчинённые)

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

Ускоряем работу: индексы и частые ошибки

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

ТЗ.Индексы.Добавить("Номенклатура, Склад");

После этого НайтиСтроки и Найти по этим колонкам работают на порядок быстрее. Набор колонок в отборе должен совпадать с индексом (или начинаться с его левых колонок) — иначе индекс не применится.

Ещё несколько приёмов, которые экономят время и память:

  • 🗑 Удаление строк по условию — только обратным циклом по индексу, иначе собьются номера строк:
Для Индекс = ТЗ.Количество() - 1 По 0 Цикл
    Если ТЗ[Индекс].Количество = 0 Тогда
        ТЗ.Удалить(Индекс);
    КонецЕсли;
КонецЦикла;
  • ⚙️ Обработка на сервере. Таблицу значений можно передавать между клиентом и сервером, но это сериализация всего объёма. Большие таблицы собирайте и обрабатывайте в серверных методах (&НаСервере), а на клиент возвращайте только итог.
  • 📊 Вывод пользователю. Напрямую на управляемую форму таблицу значений не положить — на форме используется реквизит с типом «ТаблицаЗначений» (ДанныеФормыКоллекция). Для разовой выдачи проще вывести таблицу в табличный документ или в сообщение.

Диагностика типичных сбоев при обработке таблицы:

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

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

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