Доступ к данным ТаблицыЗначений: индексы, имена колонок, поиск и обход в коде 1С
Содержание
К строке таблицы значений в 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";
Результат = Запрос.Выполнить().Выгрузить();
Таблица-параметр сначала обязательно помещается во временную таблицу (ПОМЕСТИТЬ), напрямую соединять её с другими источниками нельзя. И все её колонки должны быть типизированы.
Как ускорить поиск в больших таблицах значений
На сотнях строк разница незаметна. На десятках и сотнях тысяч — заметна сильно. Что реально помогает:
- Индекс под поиск. Если в цикле много раз вызываете
НайтиСтрокипо одним и тем же колонкам, добавьте индекс один раз до цикла:ТЗ.Индексы.Добавить("Номенклатура, Склад"). Без индекса каждый поиск перебирает всю таблицу. - Состав индекса = состав отбора. Индекс по «Номенклатура, Склад» ускоряет отбор по этим двум полям. Если в структуре отбора другие колонки, индекс не поможет.
- Не плодите копии.
Скопировать()в цикле создаёт новую таблицу на каждом шаге. Лучше один разНайтиСтрокиили одна свёртка. - Тяжёлое — в запрос. Соединения, группировки с условиями, отборы по диапазонам запрос делает быстрее, чем вложенные циклы.
- Меньше колонок. Выгружайте из запроса только нужные поля: память под таблицу растёт с каждой колонкой.
Индекс ускоряет поиск, но замедляет добавление строк, потому что платформа поддерживает его при каждой вставке. Заполняйте таблицу, потом добавляйте индекс, потом ищите.
Сортировка, группировка и итоги
Сортировать
ТЗ.Сортировать("Сумма Убыв, Номенклатура");
По умолчанию сортировка по возрастанию, Убыв — по убыванию. Если нужен порядок по коду или по реквизиту ссылки, надёжнее отсортировать данные ещё в запросе (УПОРЯДОЧИТЬ ПО) или завести отдельную колонку с этим значением.
Свернуть
Свернуть — аналог СГРУППИРОВАТЬ ПО в памяти. Первый параметр — колонки группировки, второй — колонки суммирования:
ТЗ.Свернуть("Номенклатура, Склад", "Количество, Сумма");
Колонки, которые не попали ни в один из параметров, исчезнут. Если таблица до свёртки нужна целиком, сделайте копию: ТЗИтоги = ТЗ.Скопировать(); ТЗИтоги.Свернуть(...).
Пример: 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, — дубли.
Частые вопросы
Можно ли обратиться к таблице значений с клиента?
Как получить список уникальных значений колонки?
Копия = ТЗ.Скопировать(, "Номенклатура"); Копия.Свернуть("Номенклатура");. Затем ВыгрузитьКолонку даст массив без повторов.Чем НайтиСтроки отличается от Найти?
Как переименовать колонку?
ТЗ.Колонки.Найти("Сумма").Имя = "СуммаСНДС". Данные в колонке сохраняются.Можно ли хранить таблицу значений в реквизите справочника?
Новый ХранилищеЗначения(ТЗ) при записи и .Получить() при чтении. Искать по такому реквизиту запросом нельзя.