Доступ к данным ТаблицыЗначений: индексы, имена колонок, поиск и обход в коде 1С

· 9 мин

Содержание

К строке таблицы значений в 1С обращаются по индексу с нуля: ТЗ[0] — первая строка, ТЗ[ТЗ.Количество() - 1] — последняя. К значению колонки — через точку или по имени в квадратных скобках: ТЗ[0].Сумма или ТЗ[0]["Сумма"]. Найти строку по значению помогают методы Найти() и НайтиСтроки(), обойти все строки — цикл Для Каждого.

Работает это только там, где тип ТаблицаЗначений вообще доступен: на сервере, в толстом клиенте, во внешнем соединении. В тонком и веб-клиенте его нет, и форма видит таблицу уже как коллекцию данных формы. Ниже — способы обращения к данным с кодом, который можно вставлять в серверную процедуру как есть.

Как устроена таблица значений и когда она нужна

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

Типичные случаи:

  • 📥 результат запроса, выгруженный методом Выгрузить(), чтобы пройтись по нему несколько раз;
  • 🧮 промежуточный расчёт: распределение суммы по строкам, сверка двух выборок;
  • 📤 подготовка данных для печатной формы, файла обмена или загрузки в документ;
  • 🔁 параметр для запроса: таблицу передают в &Параметр и кладут во временную таблицу.

Объект состоит из трёх коллекций, к которым тоже можно обращаться:

ЧтоКак обратитьсяЧто вернёт
СтрокиТЗ[Индекс], ТЗ.Получить(Индекс)СтрокаТаблицыЗначений
КолонкиТЗ.Колонки, ТЗ.Колонки.Найти("Имя")КоллекцияКолонокТаблицыЗначений / колонка или Неопределено
ИндексыТЗ.Индексыколлекция индексов для ускорения поиска

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

Создание таблицы значений и описание колонок

Таблицу создают конструктором, колонки добавляют сразу с типом. Тип задаёт объект ОписаниеТипов.

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

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

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

Третий способ — скопировать структуру существующей таблицы без строк: ТЗНовая = ТЗ.СкопироватьКолонки();.

Колонку можно добавить и без типа: ТЗ.Колонки.Добавить("Сумма"). В коде это работает, но такую таблицу нельзя передать в запрос как источник временной таблицы — платформа потребует типизированные колонки.

Обращение к строкам и колонкам таблицы значений

Это главное, ради чего открывают справку. Все варианты в одном месте.

Строка по индексу

ПерваяСтрока    = ТЗ[0];
ПоследняяСтрока = ТЗ[ТЗ.Количество() - 1];
ТретьяСтрока    = ТЗ.Получить(2);   // то же, что ТЗ[2]
НомерВТаблице   = ТЗ.Индекс(ПерваяСтрока); // вернёт 0

Индексы начинаются с нуля. Перед обращением по индексу проверяйте, что таблица не пустая: Если ТЗ.Количество() > 0 Тогда.

Значение колонки в строке

Три равноценных формы записи:

Строка = ТЗ[0];
Сумма1 = Строка.Сумма;        // по имени через точку
Сумма2 = Строка["Сумма"];     // по имени строкой
Сумма3 = Строка[2];           // по номеру колонки, тоже с нуля

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

ИмяКолонки = "Количество";
Для Каждого Строка Из ТЗ Цикл
	Строка[ИмяКолонки] = Строка[ИмяКолонки] * 2;
КонецЦикла;

Колонка целиком

  • ТЗ.ВыгрузитьКолонку("Номенклатура") — вернёт массив значений колонки по всем строкам;
  • ТЗ.ЗагрузитьКолонку(Массив, "Сумма") — запишет массив в колонку построчно;
  • ТЗ.Итог("Сумма") — сумма числовой колонки;
  • ТЗ.Колонки.Найти("Сумма") <> Неопределено — проверка, что колонка есть.

Обход всех строк

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

Если нужен номер строки, используйте цикл по счётчику: Для Сч = 0 По ТЗ.Количество() - 1 Цикл Строка = ТЗ[Сч]; ....

Переменная цикла — ссылка на саму строку таблицы, а не копия. Присвоили Строка.Сумма = 0 — изменилась строка в таблице.

Таблица значений в форме и на клиенте

На управляемой форме таблица значений не может быть на клиенте. Реквизит формы типа ТаблицаЗначений на клиенте выглядит как ДанныеФормыКоллекция: к строкам обращаются так же по индексу, но часть методов, например Свернуть и Скопировать, там отсутствует.

Обычный приём — переложить данные на сервере:

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

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

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

Добавить и вставить

НоваяСтрока = ТЗ.Добавить();          // в конец
НоваяСтрока.Номенклатура = Товар;
НоваяСтрока.Количество   = 5;
НоваяСтрока.Сумма        = 1250;

СтрокаВНачале = ТЗ.Вставить(0);       // перед первой строкой

Быстро скопировать значения одноимённых полей из другой строки или выборки — ЗаполнитьЗначенияСвойств(НоваяСтрока, Источник). Это экономит десяток строк присваиваний.

Изменить значения

  • одно значение — обычным присваиванием Строка.Сумма = 100;
  • одно значение во все строки — ТЗ.ЗаполнитьЗначения(0, "Сумма, Количество");
  • переставить строку — ТЗ.Сдвинуть(Строка, -1) поднимет её на одну позицию вверх.

Удалить

ТЗ.Удалить(Строка) принимает строку или её индекс. ТЗ.Очистить() удаляет все строки, колонки остаются.

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

// 1. Обход с конца по зафиксированному количеству
ВсегоСтрок = ТЗ.Количество();
Для Сч = 1 По ВсегоСтрок Цикл
	Строка = ТЗ[ВсегоСтрок - Сч];
	Если Строка.Количество = 0 Тогда
		ТЗ.Удалить(Строка);
	КонецЕсли;
КонецЦикла;

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

Во втором способе цикл идёт по массиву найденных строк, а не по самой таблице, поэтому сдвиг ничего не ломает.

Поиск строк и отбор данных в таблице значений

МетодЧто ищетРезультат
Найти(Значение, "Колонки")первое совпадение значения в указанных колонкахстрока или Неопределено
НайтиСтроки(Структура)все строки, где совпадают все поля структурымассив строк (может быть пустым)
Скопировать(Структура, "Колонки")то же, что НайтиСтроки, но сразу новая таблицаТаблицаЗначений
// Первая строка с этим товаром
Строка = ТЗ.Найти(Товар, "Номенклатура");
Если Строка <> Неопределено Тогда
	Строка.Количество = Строка.Количество + 1;
КонецЕсли;

// Все строки товара на конкретном складе
Отбор = Новый Структура("Номенклатура, Склад", Товар, Склад);
Строки = ТЗ.НайтиСтроки(Отбор);
Сообщить("Найдено строк: " + Формат(Строки.Количество(), "ЧН=0; ЧГ=0"));

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

НайтиСтроки ищет только по равенству. Условия «больше», «содержит», «не равно» в структуру не передать. Для них два пути: цикл с Если или запрос к таблице через временную таблицу.

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

Таблица-параметр сначала обязательно помещается во временную таблицу (ПОМЕСТИТЬ), напрямую соединять её с другими источниками нельзя. И все её колонки должны быть типизированы.

Как ускорить поиск в больших таблицах значений

На сотнях строк разница незаметна. На десятках и сотнях тысяч — заметна сильно. Что реально помогает:

  1. Индекс под поиск. Если в цикле много раз вызываете НайтиСтроки по одним и тем же колонкам, добавьте индекс один раз до цикла: ТЗ.Индексы.Добавить("Номенклатура, Склад"). Без индекса каждый поиск перебирает всю таблицу.
  2. Состав индекса = состав отбора. Индекс по «Номенклатура, Склад» ускоряет отбор по этим двум полям. Если в структуре отбора другие колонки, индекс не поможет.
  3. Не плодите копии. Скопировать() в цикле создаёт новую таблицу на каждом шаге. Лучше один раз НайтиСтроки или одна свёртка.
  4. Тяжёлое — в запрос. Соединения, группировки с условиями, отборы по диапазонам запрос делает быстрее, чем вложенные циклы.
  5. Меньше колонок. Выгружайте из запроса только нужные поля: память под таблицу растёт с каждой колонкой.

Индекс ускоряет поиск, но замедляет добавление строк, потому что платформа поддерживает его при каждой вставке. Заполняйте таблицу, потом добавляйте индекс, потом ищите.

Сортировка, группировка и итоги

Сортировать

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

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

Свернуть

Свернуть — аналог СГРУППИРОВАТЬ ПО в памяти. Первый параметр — колонки группировки, второй — колонки суммирования:

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

Колонки, которые не попали ни в один из параметров, исчезнут. Если таблица до свёртки нужна целиком, сделайте копию: ТЗИтоги = ТЗ.Скопировать(); ТЗИтоги.Свернуть(...).

Пример: 4 строки — товар А на 100 и 150 рублей, товар Б на 200 и 50 рублей. После Свернуть("Номенклатура", "Сумма") останется 2 строки: А — 250, Б — 250.

Итог и количество

  • ТЗ.Итог("Сумма") — сумма по колонке без свёртки;
  • ТЗ.Количество() — число строк;
  • ТЗ.Колонки.Количество() — число колонок.

Частые ошибки при обращении к таблице значений

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

Ещё одна тихая ошибка — сравнение строк таблицы по значению. ТЗ[0] = ТЗ2[0] сравнивает сами объекты строк, а не их содержимое, и даст Ложь даже при одинаковых значениях. Сравнивайте нужные колонки по отдельности.

Практические примеры из реальных задач

Сводка остатков по складам

Нужно получить по каждому товару сумму остатка по всем складам и найти товары без остатка.

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

Распределение суммы по строкам

Скидку 1000 рублей нужно раскидать на три строки пропорционально суммам 300, 500 и 200 рублей. Остаток от округления уходит в последнюю строку.

Скидка = 1000;
Всего  = ТЗ.Итог("Сумма");        // 1000
Распределено = 0;
Для Сч = 0 По ТЗ.Количество() - 1 Цикл
	Строка = ТЗ[Сч];
	Если Сч = ТЗ.Количество() - 1 Тогда
		Строка.Скидка = Скидка - Распределено;
	Иначе
		Строка.Скидка = Окр(Скидка * Строка.Сумма / Всего, 2);
		Распределено = Распределено + Строка.Скидка;
	КонецЕсли;
КонецЦикла;

Получится 300, 500 и 200 рублей. Если суммы дадут копейки при делении, разница не потеряется, а осядет в последней строке. Перед делением на Всего добавьте проверку на ноль.

Поиск дублей

Копия = ТЗ.Скопировать(, "Номенклатура");
Копия.Колонки.Добавить("Повторов", Новый ОписаниеТипов("Число"));
Копия.ЗаполнитьЗначения(1, "Повторов");
Копия.Свернуть("Номенклатура", "Повторов");
Для Каждого СтрокаТЗ Из Копия Цикл
	Если СтрокаТЗ.Повторов > 1 Тогда
		Сообщить(Строка(СтрокаТЗ.Номенклатура) + " — повторов: " + Формат(СтрокаТЗ.Повторов, "ЧГ=0"));
	КонецЕсли;
КонецЦикла;

После свёртки в колонке «Повторов» окажется число вхождений каждого товара. Всё, что больше 1, — дубли.

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

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