Чтение данных из таблицы значений в 1С 8.3: индекс, Найти, НайтиСтроки, отбор и запрос
Содержание
Значение из таблицы значений 1С получают в два шага: сначала берут строку, потом обращаются к колонке по имени. Например, ТЗ[0].Сумма вернёт сумму из первой строки, а ТЗ.Найти(Контрагент, "Контрагент").Сумма — сумму из первой строки с нужным контрагентом. Всё остальное — способы быстро и безопасно найти эту строку.
Типичная ситуация: обработка собрала остатки в таблицу значений, и теперь для каждой номенклатуры нужно достать количество и цену. Код с вложенным циклом работает на 100 строках и «вешает» сеанс на 50 000. Разберём, какой метод чтения выбрать под задачу и где его подстерегают ошибки.
Как устроена таблица значений и как обратиться к ячейке
Таблица значений (ТаблицаЗначений) — коллекция строк. У каждой строки есть значения во всех колонках таблицы. Колонки описаны в свойстве Колонки, строки нумеруются с нуля.
Три базовых способа достать значение:
- 🔢 по индексу строки —
ТЗ[0]возвращает первую строку (СтрокаТаблицыЗначений); - 🏷️ по имени колонки через точку —
Строка.Сумма; - 🧩 по имени колонки в квадратных скобках —
Строка["Сумма"]. Удобно, когда имя колонки хранится в переменной.
ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
ТЗ.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число"));
ТЗ.Колонки.Добавить("Сумма", Новый ОписаниеТипов("Число"));
// Первая строка, колонка «Сумма» — три равноценные записи
Сумма1 = ТЗ[0].Сумма;
Сумма2 = ТЗ[0]["Сумма"];
ИмяКолонки = "Сумма";
Сумма3 = ТЗ[0][ИмяКолонки];
// Последняя строка
Последняя = ТЗ[ТЗ.Количество() - 1];
Есть и обращение к колонке по её номеру: Строка.Получить(1) вернёт значение второй колонки. В прикладном коде так пишут редко — при добавлении колонки номера сдвигаются, и код молча начинает читать не то.
Перебор всех строк
Когда нужно пройти по всей таблице, используйте Для Каждого:
Итого = 0;
Для Каждого Строка Из ТЗ Цикл
Если Строка.Количество > 0 Тогда
Итого = Итого + Строка.Сумма;
КонецЕсли;
КонецЦикла;
Для простой суммы по колонке цикл не нужен: ТЗ.Итог("Сумма") вернёт итог по числовой колонке одной строкой кода.
Полезные служебные методы
| Задача | Метод | Что вернёт |
|---|---|---|
| Номер строки в таблице | ТЗ.Индекс(Строка) | Число, отсчёт с 0 |
| Все значения колонки | ТЗ.ВыгрузитьКолонку("Номенклатура") | Массив |
| Сумма по колонке | ТЗ.Итог("Сумма") | Число |
| Число строк | ТЗ.Количество() | Число |
| Есть ли колонка | ТЗ.Колонки.Найти("Сумма") | Колонку или Неопределено |
| Перенести строку в структуру | ЗаполнитьЗначенияСвойств(Структура, Строка) | Заполнит одноимённые ключи |
Поиск одной строки методом Найти
Метод Найти ищет первое вхождение значения и возвращает строку целиком. Если ничего не найдено — Неопределено.
Синтаксис: ТЗ.Найти(Значение, "Колонка1, Колонка2").
Второй параметр ограничивает колонки поиска. Без него платформа ищет значение во всех колонках. Это медленнее, и можно получить ложное совпадение: число 5 найдётся и в «Количестве», и в «Сумме».
Строка = ТЗ.Найти(Номенклатура, "Номенклатура");
Если Строка <> Неопределено Тогда
Цена = Строка.Сумма / Строка.Количество;
Иначе
Цена = 0;
КонецЕсли;
Всегда проверяйте результат Найти на Неопределено перед обращением к колонке. Иначе при отсутствии строки получите исключение и прерванную обработку.
Найти подходит, когда условие одно, а нужная строка заведомо уникальна: курс по валюте, цена по номенклатуре, остаток по складу.
Если в колонке несколько строк с одинаковым значением, Найти вернёт только первую. Порядок строк зависит от того, как таблицу заполняли и сортировали. Для «всех подходящих» нужен следующий метод.
НайтиСтроки: выборка строк по нескольким условиям
Метод НайтиСтроки принимает структуру условий и возвращает Массив строк. Ключ структуры — имя колонки, значение — то, чему колонка должна быть равна. Все условия объединяются через «И».
Отбор = Новый Структура;
Отбор.Вставить("Склад", ОсновнойСклад);
Отбор.Вставить("Номенклатура", Номенклатура);
НайденныеСтроки = ТЗ.НайтиСтроки(Отбор);
Количество = 0;
Для Каждого Строка Из НайденныеСтроки Цикл
Количество = Количество + Строка.Количество;
КонецЦикла;
Если НайденныеСтроки.Количество() = 0 Тогда
// строк с таким складом и номенклатурой нет
КонецЕсли;
Что нужно знать о НайтиСтроки:
- ✅ возвращает пустой массив, а не
Неопределено, если ничего не нашлось — проверка черезКоличество() = 0; - 🔗 в массиве лежат ссылки на строки исходной таблицы: изменение
Строка.Суммаменяет данные в самой ТЗ; - ⚖️ сравнение только на равенство. Условия «больше», «меньше», «содержит» метод не поддерживает;
- 📋 вместо структуры можно передать
ФиксированнуюСтруктуру— удобно, когда отбор приходит параметром.
Индексы ускоряют поиск
Без индекса НайтиСтроки просматривает таблицу целиком. Если таблицу опрашивают в цикле тысячи раз, добавьте индекс по колонкам отбора:
ТЗ.Индексы.Добавить("Склад, Номенклатура");
Индекс строят один раз, после заполнения таблицы или до него — платформа поддерживает его при добавлении строк сама. Набор колонок в индексе должен совпадать с ключами структуры отбора. Индекс по «Склад, Номенклатура» не поможет поиску только по «Номенклатуре» — под такой поиск нужен отдельный индекс.
Индекс занимает память и замедляет добавление строк. На таблице в 200 строк, которую ищут пару раз, он не нужен.
Отбор строк: копия таблицы и фильтр на форме
У самой таблицы значений нет свойства «Отбор», как у набора записей регистра. Фильтруют её тремя способами.
Отобранная копия через Скопировать
Скопировать принимает структуру отбора или массив строк и возвращает новую таблицу значений. Вторым параметром можно оставить только нужные колонки.
Отбор = Новый Структура("Склад", ОсновнойСклад);
ТЗСклада = ТЗ.Скопировать(Отбор, "Номенклатура, Количество");
// Или из ранее найденного массива строк
ТЗНайденные = ТЗ.Скопировать(ТЗ.НайтиСтроки(Отбор));
Копия — независимая таблица. Изменения в ней не попадают в исходную ТЗ. Это главное отличие от массива, который вернул НайтиСтроки.
Отбор с условием «больше/меньше»
Когда условие сложнее равенства, соберите массив вручную и при необходимости передайте его в Скопировать:
КрупныеСтроки = Новый Массив;
Для Каждого Строка Из ТЗ Цикл
Если Строка.Сумма >= 100000 Тогда
КрупныеСтроки.Добавить(Строка);
КонецЕсли;
КонецЦикла;
ТЗКрупные = ТЗ.Скопировать(КрупныеСтроки);
Фильтр в табличном поле управляемой формы
Если таблица выведена на форму, данные в реквизите формы хранятся как ДанныеФормыКоллекция. Чтобы показать пользователю только часть строк, задайте элементу формы свойство ОтборСтрок:
Элементы.Товары.ОтборСтрок = Новый ФиксированнаяСтруктура("Склад", ОсновнойСклад);
// Снять отбор
Элементы.Товары.ОтборСтрок = Неопределено;
Это фильтр только отображения. Строки из реквизита никуда не деваются, и обход реквизита в коде вернёт все строки.
Значение текущей строки на клиенте читают через Элементы.Товары.ТекущиеДанные. Перед обращением к полям проверьте, что оно не равно Неопределено — в пустой таблице текущей строки нет.
Какой способ чтения быстрее
Скорость зависит от размера таблицы и того, сколько раз вы её опрашиваете. Ориентируйтесь на таблицу:
| Способ | Когда подходит | Слабое место |
|---|---|---|
ТЗ[Индекс] | Номер строки известен заранее | Нет поиска по значению |
Для Каждого + Если | Один проход, сложное условие | Проход по всем строкам каждый раз |
Найти с колонкой | Одна строка по одному значению | Только первое совпадение |
НайтиСтроки без индекса | Разовая выборка по равенству | Полный просмотр таблицы |
НайтиСтроки с индексом | Много поисков по одним и тем же колонкам | Память и время на индекс |
Соответствие по ключу | Тысячи поисков по одному полю | Нужно построить заранее |
| Запрос к ТЗ | Группировка, соединение с базой | Выгрузка во временную таблицу, только на сервере |
Главная ошибка производительности — поиск внутри цикла по другой большой таблице без индекса. Каждый проход внешнего цикла заново просматривает внутреннюю таблицу. Время растёт как произведение числа строк обеих таблиц.
Решения в порядке простоты:
- Добавить индекс по колонкам поиска во внутренней таблице.
- Один раз собрать
Соответствие«ключ → строка» и дальше брать значение по ключу. - Соединить обе таблицы в одном запросе.
// Соответствие для быстрого доступа по одному ключу
Цены = Новый Соответствие;
Для Каждого Строка Из ТЗЦены Цикл
Цены.Вставить(Строка.Номенклатура, Строка.Цена);
КонецЦикла;
Для Каждого Строка Из ТЗТовары Цикл
Цена = Цены.Получить(Строка.Номенклатура);
Строка.Сумма = Строка.Количество * ?(Цена = Неопределено, 0, Цена);
КонецЦикла;
Соответствие хранит одно значение на ключ. Если в ТЗЦены номенклатура повторяется, останется цена из последней строки.
Не верите в выигрыш — замерьте. Включите замер производительности в отладчике или сравните ТекущаяУниверсальнаяДатаВМиллисекундах() до и после фрагмента.
Ошибки при чтении значений и как их обработать
Большинство исключений при работе с таблицей значений сводится к пяти ситуациям.
ТЗ.Колонки.Найти("Сумма") и псевдонимы в тексте запроса.Неопределено. Обычно это результат Найти, который не нашёл строку, или пустые ТекущиеДанные на форме. Добавьте проверку на Неопределено.ТЗ.Количество(), либо таблица пуста. Строки нумеруются с нуля: последняя — ТЗ.Количество() - 1.ОписаниеТипов.Для Каждого сдвигает строки, часть пропускается. Удаляйте обратным циклом по индексу или соберите строки в массив и удалите после цикла.Безопасное чтение без исключений
Если колонка может отсутствовать — таблица приходит из внешней обработки или от другого разработчика, — проверьте её заранее:
Функция ЗначениеКолонки(Строка, ИмяКолонки, ПоУмолчанию = Неопределено)
Если Строка = Неопределено Тогда
Возврат ПоУмолчанию;
КонецЕсли;
Если Строка.Владелец().Колонки.Найти(ИмяКолонки) = Неопределено Тогда
Возврат ПоУмолчанию;
КонецЕсли;
Возврат Строка[ИмяКолонки];
КонецФункции
Метод Владелец() возвращает таблицу, которой принадлежит строка. Так функция работает с любой ТЗ без передачи самой таблицы параметром.
Обработку через Попытка … Исключение оставьте для действительно непредсказуемых случаев. Явная проверка дешевле и понятнее при чтении кода.
Удаление строк по условию
Правильный обратный цикл выглядит так:
Всего = ТЗ.Количество();
Для Сч = 1 По Всего Цикл
Строка = ТЗ[Всего - Сч];
Если Строка.Количество = 0 Тогда
ТЗ.Удалить(Строка);
КонецЕсли;
КонецЦикла;
Индекс считается от зафиксированного Всего: удаление идёт с конца, поэтому строки, которые ещё предстоит проверить, не сдвигаются.
Запрос к таблице значений: группировка и соединение
Когда нужно сгруппировать строки, посчитать итоги по нескольким полям или соединить ТЗ с данными базы, удобнее запрос. Таблицу передают параметром и сначала помещают во временную таблицу — выбирать поля прямо из параметра с соединениями платформа не даёт.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Т.Номенклатура КАК Номенклатура,
| Т.Количество КАК Количество,
| Т.Сумма КАК Сумма
|ПОМЕСТИТЬ ВТТовары
|ИЗ
| &Товары КАК Т
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТТовары.Номенклатура КАК Номенклатура,
| ВТТовары.Номенклатура.Артикул КАК Артикул,
| СУММА(ВТТовары.Количество) КАК Количество,
| СУММА(ВТТовары.Сумма) КАК Сумма
|ИЗ
| ВТТовары КАК ВТТовары
|СГРУППИРОВАТЬ ПО
| ВТТовары.Номенклатура,
| ВТТовары.Номенклатура.Артикул";
Запрос.УстановитьПараметр("Товары", ТЗ);
Результат = Запрос.Выполнить().Выгрузить();
Для Каждого Строка Из Результат Цикл
Сообщить(Строка.Артикул + ": " + Формат(Строка.Сумма, "ЧДЦ=2"));
КонецЦикла;
Здесь запрос делает то, что вручную потребовало бы два цикла: сворачивает дубли по номенклатуре и подтягивает артикул из справочника через разыменование ссылки.
Условия работы запроса с таблицей значений:
- 🖥️ запрос выполняется только на сервере. На клиенте объекта
Запроснет; - 🧱 у всех колонок, которые попадают в запрос, должен быть задан тип через
ОписаниеТипов; - 📏 у строковых колонок задавайте длину (
Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(100))) — по строкам неограниченной длины нельзя группировать и соединять; - 📥 результат возвращается новой таблицей через
Выгрузить(); исходная ТЗ не меняется.
Для простой свёртки без обращения к базе есть метод ТЗ.Свернуть("Номенклатура", "Количество, Сумма"). Он быстрее запроса и работает с той же таблицей на месте. Запрос нужен, когда требуются данные из базы или условия сложнее равенства.
Условие «больше» через запрос
В отличие от НайтиСтроки, в запросе доступны любые сравнения:
|ВЫБРАТЬ
| ВТТовары.Номенклатура,
| ВТТовары.Сумма
|ИЗ
| ВТТовары КАК ВТТовары
|ГДЕ
| ВТТовары.Сумма >= &Порог
На маленьких таблицах выгода сомнительна: передача ТЗ во временную таблицу сама занимает время. На больших таблицах с группировкой или соединением запрос обычно проще и быстрее ручного кода.
Частые вопросы
Как получить значение из таблицы значений на клиенте управляемой формы?
ТаблицаЗначений на тонком клиенте недоступен. Читайте реквизит формы (ДанныеФормыКоллекция) по индексу, через ТекущиеДанные элемента или передайте нужные значения с сервера.Как превратить реквизит формы в таблицу значений?
РеквизитФормыВЗначение("Товары"). Обратная операция — ЗначениеВРеквизитФормы(ТЗ, "Товары").Как взять значение колонки, если её имя хранится в строке?
Строка[ИмяКолонки]. Перед этим проверьте, что колонка есть, через ТЗ.Колонки.Найти(ИмяКолонки).Возвращает ли НайтиСтроки копии строк?
Скопировать.Как получить уникальные значения колонки?
Уник = ТЗ.Скопировать(, "Склад"); Уник.Свернуть("Склад"); затем Уник.ВыгрузитьКолонку("Склад") вернёт массив без повторов.