Отбор строк из таблицы значений, табличной части и справочника в коде 1С 8.3
Содержание
Чтобы выбрать строки в 1С по точному значению, вызовите у коллекции НайтиСтроки(Новый Структура("Колонка", Значение)) — метод вернёт массив подходящих строк. Одну первую строку даёт Найти(Значение, "Колонка"), а сложные условия («больше», «содержит», «входит в список») решает запрос или перебор в цикле.
Методы выбора строк почти одинаково работают в таблицах значений, табличных частях документов и данных формы, но различия есть, и именно на них код падает. Тема общая для платформы 8.3 и не зависит от конфигурации: БП, ЗУП и УТ используют одни и те же методы встроенного языка.
Найти и НайтиСтроки: базовые способы выбрать строку в коллекции
Два метода закрывают большинство повседневных задач. Разница — в том, сколько строк нужно и как задаётся условие.
| Метод | Что возвращает | Условие | Когда брать |
|---|---|---|---|
Найти(Значение, Колонки) | первую найденную строку или Неопределено | одно значение, ищется в перечисленных колонках | проверить наличие, взять одну строку |
НайтиСтроки(Структура) | массив строк (может быть пустым) | равенство по нескольким колонкам сразу | выбрать все строки по условию «И» |
Скопировать(Отбор, Колонки) | новая таблица значений | структура отбора или массив строк | нужна отдельная таблица для дальнейшей обработки |
Метод Найти
Найти ищет значение в указанных колонках и останавливается на первом совпадении. Второй параметр — имена колонок через запятую. Если его не передать, поиск идёт по всем колонкам.
Строка = ТаблицаТоваров.Найти(Номенклатура, "Номенклатура");
Если Строка = Неопределено Тогда
Строка = ТаблицаТоваров.Добавить();
Строка.Номенклатура = Номенклатура;
КонецЕсли;
Строка.Количество = Строка.Количество + 1;
Это классический приём «найти или добавить» — так сворачивают повторы при загрузке данных.
Метод НайтиСтроки
НайтиСтроки принимает структуру: ключ — имя колонки, значение — то, чему колонка должна быть равна. Все условия соединяются через «И».
Отбор = Новый Структура;
Отбор.Вставить("Склад", ОсновнойСклад);
Отбор.Вставить("Обработана", Ложь);
МассивСтрок = ТаблицаТоваров.НайтиСтроки(Отбор);
Для Каждого Строка Из МассивСтрок Цикл
Строка.Обработана = Истина;
КонецЦикла;
Массив содержит ссылки на те же строки исходной таблицы, а не копии. Меняете поле в найденной строке — меняется исходная таблица.
Если подходящих строк нет, НайтиСтроки возвращает пустой массив, а не Неопределено. Проверяйте МассивСтрок.Количество() = 0.
Метод Скопировать с отбором
Когда нужна отдельная таблица, а не ссылки на строки, используйте Скопировать. Первый параметр — структура отбора, второй — список колонок, которые попадут в копию:
Отбор = Новый Структура("Склад", ОсновнойСклад);
ТоварыСклада = ТаблицаТоваров.Скопировать(Отбор, "Номенклатура, Количество");
ТоварыСклада.Свернуть("Номенклатура", "Количество");
Связка Скопировать + Свернуть даёт итоги по номенклатуре одного склада в две строки кода и не портит исходную таблицу.
Другие коллекции
- 📋
Массив.Найти(Значение)— возвращает индекс элемента илиНеопределено. - 🗂
СписокЗначений.НайтиПоЗначению(Значение)— возвращает элемент списка. - 🔑
Соответствие.Получить(Ключ)— самый быстрый поиск по ключу; если ключа нет, вернётсяНеопределено.
Как выбрать строки в табличной части документа
Табличная часть объекта (ДокументОбъект.Товары) поддерживает те же Найти и НайтиСтроки. Код из предыдущего раздела переносится без изменений, если вы работаете с объектом в модуле объекта или на сервере.
ДокументОбъект = Ссылка.ПолучитьОбъект();
Отбор = Новый Структура("Номенклатура", Номенклатура);
Строки = ДокументОбъект.Товары.НайтиСтроки(Отбор);
На форме картина другая. Реквизит формы с табличной частью — это ДанныеФормыКоллекция. У неё есть НайтиСтроки(Структура) и НайтиПоИдентификатору(Идентификатор), но нет метода Найти. Поэтому код, скопированный из модуля объекта в модуль формы, иногда падает на вызове Найти — заменяйте его на НайтиСтроки с одним ключом.
Выделенные строки в таблице формы
Когда пользователь выделил несколько строк мышью, взять их можно через свойство элемента формы:
&НаКлиенте
Процедура ОбработатьВыделенные(Команда)
Для Каждого Идентификатор Из Элементы.Товары.ВыделенныеСтроки Цикл
ДанныеСтроки = Объект.Товары.НайтиПоИдентификатору(Идентификатор);
Если ДанныеСтроки <> Неопределено Тогда
ДанныеСтроки.Скидка = 5;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
ВыделенныеСтроки хранит идентификаторы, а не сами строки. Отсюда обязательная связка с НайтиПоИдентификатору. Текущую (одну активную) строку дают Элементы.Товары.ТекущиеДанные и Элементы.Товары.ТекущаяСтрока.
Показать на экране только нужные строки
Если строки надо не обработать, а скрыть лишние от пользователя, данные не трогайте. Задайте отбор отображения:
Элементы.Товары.ОтборСтрок = Новый ФиксированнаяСтруктура("Склад", ОсновнойСклад);
Сброс — присвоить Неопределено. Строки остаются в табличной части и записываются вместе с документом, просто не видны.
Выбор строк запросом: условия, которые не умеет НайтиСтроки
НайтиСтроки проверяет только равенство. Как только нужно «сумма больше 10 000», «дата в периоде», «наименование содержит слово», «группировка с суммой» — переходите на запрос.
Запрос к табличной части в базе
Если документ записан, читать его табличную часть объектом не нужно. Запрос заберёт только нужные строки и только нужные колонки:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Т.НомерСтроки,
| Т.Номенклатура,
| Т.Количество,
| Т.Сумма
|ИЗ
| Документ.РеализацияТоваровУслуг.Товары КАК Т
|ГДЕ
| Т.Ссылка = &Ссылка
| И Т.Сумма > &МинСумма";
Запрос.УстановитьПараметр("Ссылка", Ссылка);
Запрос.УстановитьПараметр("МинСумма", 10000);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
// Выборка.Номенклатура, Выборка.Сумма
КонецЦикла;
Имя документа подставьте своё: в БП 3.0 и УТ 11 реализация называется РеализацияТоваровУслуг, но в доработанной конфигурации всё может быть иначе.
Запрос к таблице значений из памяти
Таблицу значений можно передать в запрос параметром. Надёжная схема — сначала поместить её во временную таблицу, затем выбирать с любыми условиями:
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
Запрос.Текст =
"ВЫБРАТЬ
| Т.Номенклатура,
| Т.Количество,
| Т.Сумма
|ПОМЕСТИТЬ ВТ_Товары
|ИЗ
| &ТаблицаТоваров КАК Т
|;
|ВЫБРАТЬ
| ВТ.Номенклатура,
| СУММА(ВТ.Сумма) КАК Сумма
|ИЗ
| ВТ_Товары КАК ВТ
|ГДЕ
| ВТ.Количество > 0
|СГРУППИРОВАТЬ ПО
| ВТ.Номенклатура";
Запрос.УстановитьПараметр("ТаблицаТоваров", ТаблицаТоваров);
Результат = Запрос.Выполнить().Выгрузить();
У колонок передаваемой таблицы должны быть заданы типы. Колонку, созданную через Колонки.Добавить("Сумма") без описания типов, запрос не поймёт. Создавайте так: Колонки.Добавить("Сумма", Новый ОписаниеТипов("Число")).
Запрос к таблице значений выполняется на сервере. С клиента так не сделать: вызывайте серверную процедуру и передавайте ей данные.
Полезные конструкции для отбора
- 🔢
ВЫБРАТЬ ПЕРВЫЕ 10— ограничить число строк. - 📚
ГДЕ Т.Склад В (&СписокСкладов)— параметр-массив или список значений. - 🔤
ГДЕ Т.Номенклатура.Наименование ПОДОБНО "%кабель%"— поиск по части строки. - 📅
ГДЕ Т.Период МЕЖДУ &Начало И &Конец— диапазон дат.
Перебор строк с условием: когда цикл честнее запроса
Цикл Для Каждого с Если внутри — самый гибкий способ выбрать строки. Он понятен, работает на клиенте и на сервере, не требует типизированных колонок. Подходит для таблиц на сотни и тысячи строк.
Найденные = Новый Массив;
Для Каждого Строка Из ТаблицаТоваров Цикл
Если Строка.Сумма >= 10000
И СтрНайти(НРег(Строка.Комментарий), "срочно") > 0 Тогда
Найденные.Добавить(Строка);
КонецЕсли;
КонецЦикла;
СтрНайти доступна при режиме совместимости конфигурации не ниже 8.3.6. В старых конфигурациях используйте Найти(Строка, Подстрока) — глобальную функцию поиска подстроки, не путая её с методом таблицы.
Удаление выбранных строк
Удалять строки внутри прямого цикла Для Каждого нельзя: после удаления сдвигаются индексы, и часть строк проскакивает. Два безопасных варианта.
Первый — собрать строки и удалить их потом:
КУдалению = ТаблицаТоваров.НайтиСтроки(Новый Структура("Количество", 0));
Для Каждого Строка Из КУдалению Цикл
ТаблицаТоваров.Удалить(Строка);
КонецЦикла;
Второй — обратный проход по индексу, с границей, вычисленной один раз до цикла:
Всего = ТаблицаТоваров.Количество();
Для Сч = 1 По Всего Цикл
Индекс = Всего - Сч;
Если ТаблицаТоваров[Индекс].Количество = 0 Тогда
ТаблицаТоваров.Удалить(Индекс);
КонецЕсли;
КонецЦикла;
При Всего = 3 индекс идёт 2, 1, 0. Удаление строки с большим индексом не сдвигает строки, до которых цикл ещё не дошёл.
Выбор элементов справочника
Для справочников у менеджера есть готовые методы поиска:
| Метод | Пример | Особенность |
|---|---|---|
НайтиПоКоду | Справочники.Номенклатура.НайтиПоКоду("00-00000123") | код передаётся строкой с ведущими нулями |
НайтиПоНаименованию | Справочники.Склады.НайтиПоНаименованию("Основной склад", Истина) | Истина — точное совпадение |
НайтиПоРеквизиту | Справочники.Контрагенты.НайтиПоРеквизиту("ИНН", ИНН) | вернёт первый найденный элемент |
Если элемент не найден, методы возвращают пустую ссылку, а не Неопределено. Проверка — Если Результат.Пустая() Тогда. Когда совпадений может быть несколько (два контрагента с одним ИНН), берите запрос: НайтиПоРеквизиту молча отдаст только один.
Скорость: какой метод выбора строк быстрее
На таблице из 200 строк разницы нет — пишите как удобнее. Проблемы начинаются, когда поиск вызывается внутри цикла по другой большой таблице. Тысяча вызовов Найти по таблице в 50 000 строк без индекса — это до 50 миллионов сравнений.
Индексы таблицы значений
Индекс строится один раз, после этого НайтиСтроки по проиндексированным колонкам не перебирает таблицу целиком:
ТаблицаОстатков.Индексы.Добавить("Номенклатура, Склад");
// далее много вызовов
Строки = ТаблицаОстатков.НайтиСтроки(Новый Структура("Номенклатура, Склад", Товар, Склад));
Набор колонок в отборе должен совпадать с набором в индексе. Индекс по «Номенклатура, Склад» не ускорит отбор только по «Склад».
Сравнение подходов
| Подход | Скорость на больших объёмах | Где работает | Ограничения |
|---|---|---|---|
Найти без индекса | медленно, перебор | клиент и сервер | только одно значение |
НайтиСтроки с индексом | быстро | сервер, таблица значений | только равенство |
Соответствие по ключу | быстро | клиент и сервер | ключ — одно значение, нужна сборка |
| Запрос | быстро, особенно к данным в базе | только сервер | типизированные колонки, накладные расходы на вызов |
| Перебор в цикле | медленно, линейно | клиент и сервер | зато любые условия |
Практические приёмы
- Вынесите поиск из вложенного цикла. Постройте индекс или
Соответствиеодин раз, потом ищите. - Не читайте документ объектом ради одной колонки.
ПолучитьОбъект()загружает все табличные части; запрос берёт только нужное. - Не гоняйте таблицы между клиентом и сервером ради отбора. Отберите на сервере и верните на форму результат.
- Если нужен только список значений колонки —
ТаблицаТоваров.ВыгрузитьКолонку("Номенклатура")вернёт массив без цикла.
Ошибки при выборе строк и как их избежать
«Значение не является значением объектного типа»
Классика после Найти: строка не нашлась, вернулось Неопределено, а код сразу обращается к Строка.Количество. Лечится проверкой результата перед использованием.
«Поле объекта не обнаружено»
Опечатка в имени колонки — в строке Колонки метода Найти, в ключе структуры или в обращении к полю. Сверьте имя с тем, как колонка создана. Для табличной части — с именем реквизита в конфигураторе.
Пустой результат, хотя строки видны
- 🔁 Сравниваются значения разных типов: в колонке ссылка, а ищете строку с наименованием.
Найтисравнивает значения, а не их представление. - 🔢 Число хранится в колонке как строка (после загрузки из Excel).
100и"100"не равны. - ✂️ В строке хвостовые пробелы. Перед поиском применяйте
СокрЛП. - 🗓 Дата со временем:
НайтиСтрокипо дате01.03не найдёт строку с01.03 14:25. Отбирайте поНачалоДняили запросом сМЕЖДУ.
Изменения «не сохраняются»
Если строки отобраны через Скопировать, вы правите копию. Исходная таблица остаётся прежней. Нужен отбор со ссылками на исходные строки — используйте НайтиСтроки.
На форме обратная история: правки в Объект.Товары попадают в базу только после записи документа. Если код меняет строки в серверной процедуре через ПолучитьОбъект(), форма о них не узнает, а запись формы перезатрёт изменения. Меняйте данные в одном месте — либо в реквизите формы, либо в объекте с последующим перечитыванием.