Множественные значения в реквизите 1С: от массива до таблицы значений
Содержание
Сам реквизит хранит ровно одно значение своего типа — «список» или «массив» в типе реквизита выбрать нельзя. Чтобы держать в нём несколько значений, есть три пути: работать с коллекцией в памяти (Массив, СписокЗначений, ТаблицаЗначений), упаковать коллекцию в реквизит типа ХранилищеЗначения или вынести данные в табличную часть либо регистр.
Какой способ выбрать, зависит от двух вопросов: нужно ли показывать значения пользователю и нужно ли искать по ним запросом. Разберём каждую коллекцию, затем — как это сохранить в базе, и чем за хранение множественных значений в одном реквизите приходится платить в производительности.
Массив: добавление, перебор, доступ по индексу — без лишних полей.СписокЗначений: есть представление, пометка и готовый диалог выбора.ТаблицаЗначений: колонки с типами, итоги, свёртка, отбор строк.ХранилищеЗначения — упакуйте туда массив или таблицу.Почему в реквизит нельзя записать сразу несколько значений
Когда вы создаёте реквизит справочника, документа или регистра, платформа просит указать его тип: число, строка, ссылка, дата. Все эти типы хранят одно значение. Типов «массив» или «список» в этом окне нет — и это не ограничение конкретной конфигурации, а устройство модели данных 1С.
Коллекции (Массив, СписокЗначений, ТаблицаЗначений) — это объекты, которые живут в оперативной памяти во время выполнения кода. Напрямую положить такой объект в поле таблицы базы нельзя. Поэтому задача распадается на две:
- 📦 собрать и обработать значения — в памяти, подходящей коллекцией;
- 💾 сохранить набор — через
ХранилищеЗначенияили через отдельную таблицу (табличную часть, регистр).
Дальше идём от простого к сложному.
Массив — простой перечень однотипных значений
Массив — самая лёгкая коллекция. Создаёте, добавляете элементы, обращаетесь по индексу:
СписокТоваров = Новый Массив;
СписокТоваров.Добавить(Товар1);
СписокТоваров.Добавить(Товар2);
Количество = СписокТоваров.Количество(); // 2
Первый = СписокТоваров[0];
Последний = СписокТоваров[СписокТоваров.ВГраница()];
Полезные методы: Добавить, Вставить(Индекс, Значение), Удалить(Индекс), Количество(), ВГраница() (индекс последнего элемента), Найти(Значение) — вернёт индекс или Неопределено.
Массив доступен и на клиенте, и на сервере, и свободно передаётся между ними как параметр вызова. Это важное практическое свойство: его можно вернуть с сервера на тонкий клиент без обёрток.
Когда в массиве нужно часто искать значение, Найти работает перебором — это медленно на больших объёмах. Для поиска по ключу берите Соответствие: Соответствие.Получить(Ключ) находит значение сразу, без перебора.
Рядом с массивом стоит упомянуть ещё две коллекции, которые закрывают соседние задачи:
Структура— набор именованных полей фиксированного состава:Новый Структура("Код, Наименование", 1, "Товар"), доступСтр.Код. Удобна, когда «несколько значений» — это один объект с понятными свойствами, а не однородный список.Соответствие— пары ключ-значение с мгновенным поиском по ключу:Соотв.Вставить(Ключ, Значение),Соотв.Получить(Ключ). Идеальна для справочника соответствий «вход → результат».
Обе, как и массив, доступны везде и передаются между клиентом и сервером.
СписокЗначений — когда нужны названия и пометки
СписокЗначений отличается от массива тем, что у каждого элемента есть не только значение, но и представление (текст для пользователя), пометка (галочка) и картинка. Это коллекция «с человеческим лицом» — её удобно показывать в диалоге.
Варианты = Новый СписокЗначений;
Варианты.Добавить(Контрагент1, "Поставщик", Истина);
Варианты.Добавить(Контрагент2, "Покупатель", Ложь);
Для Каждого Элемент Из Варианты Цикл
Сообщить(Элемент.Представление + ": " + Строка(Элемент.Пометка));
КонецЦикла;
У элемента доступны Элемент.Значение, Элемент.Представление, Элемент.Пометка. У самого списка — НайтиПоЗначению, СортироватьПоПредставлению(), ВыгрузитьЗначения() (вернёт массив только значений) и обратный ЗагрузитьЗначения(Массив).
Отдельный плюс — готовый интерактивный выбор. На толстом клиенте Варианты.ВыбратьЭлемент(Заголовок) сам покажет окно; в управляемом приложении список передают в ПоказатьВыборИзСписка. Берите СписокЗначений, когда пользователь должен отметить несколько пунктов или выбрать один из набора.
ТаблицаЗначений — несколько полей в каждой строке
Если в «одном значении» на самом деле несколько связанных полей — номенклатура, количество, цена, — это уже не список, а таблица. ТаблицаЗначений — коллекция строк с типизированными колонками:
ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
ТЗ.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число"));
НоваяСтрока = ТЗ.Добавить();
НоваяСтрока.Номенклатура = Товар;
НоваяСтрока.Количество = 5;
Итого = ТЗ.Итог("Количество"); // сумма по колонке
ТЗ.Свернуть("Номенклатура", "Количество"); // группировка с суммированием
Сильные стороны таблицы: отбор строк НайтиСтроки(Новый Структура("Номенклатура", Товар)), итоги Итог, группировка Свернуть, сортировка Сортировать("Количество Убыв"). Для частого поиска добавьте индекс: ТЗ.Индексы.Добавить("Номенклатура") — тогда НайтиСтроки отработает заметно быстрее.
Чаще всего таблицу не строят строка за строкой, а получают готовой из запроса: Результат.Выгрузить() возвращает ТаблицаЗначений с колонками по полям выборки. Копию с отбором делают через Скопировать(Отбор, Колонки) — удобно, когда из большой таблицы нужен фрагмент по условию. Это разом снимает вопрос «как собрать несколько значений» — платформа собирает их за вас.
ТаблицаЗначений доступна на сервере и в толстом клиенте, но не доступна напрямую на тонком и веб-клиенте и не передаётся параметром между клиентом и сервером. На форме с таблицей работают через реквизит формы (данные формы), а между клиентом и сервером гоняют не саму таблицу, а её адрес во временном хранилище либо массив структур.
Как сохранить коллекцию в базе
Всё перечисленное живёт в памяти. Чтобы набор пережил закрытие сеанса, его нужно записать. Два рабочих варианта.
Реквизит типа ХранилищеЗначения. В него можно упаковать любое сериализуемое значение — массив, список, таблицу — и получить обратно:
// упаковали перед записью объекта
Объект.ДопДанные = Новый ХранилищеЗначения(ТЗ, Новый СжатиеДанных(9));
// распаковали при чтении
ТЗ = Объект.ДопДанные.Получить();
Второй параметр Новый СжатиеДанных(9) сжимает данные (уровень 0–9). Минус способа принципиальный: содержимое ХранилищеЗначения нельзя прочитать запросом — для СУБД это непрозрачный двоичный блок. Годится для служебных данных, настроек, снимков, которые читаются только кодом целиком.
Табличная часть или регистр. Когда по значениям нужны запросы, отборы, отчёты и соединения — не коллекция в реквизите, а нормальная таблица: табличная часть объекта или регистр сведений . Это чуть больше работы на старте, зато данные остаются «видимыми» для языка запросов.
Сериализация для внешних систем. Если набор значений нужно не хранить, а передать наружу — в веб-сервис, файл обмена, стороннюю программу, — коллекцию сериализуют в текст. Массив, Структура и Соответствие штатно пишутся в JSON через ЗаписатьJSON, читаются обратно через ПрочитатьJSON. Для XML-обмена применяют СериализаторXDTO. ТаблицаЗначений напрямую в JSON не укладывается — её сначала перегоняют в массив структур (строка → структура с полями-колонками), и уже массив сериализуют. Такой текст кладут в строковый реквизит или отдают во внешнюю систему без всякого хранилища.
Короткая развилка:
| Нужно | Чем хранить |
|---|---|
| Набор читается только кодом, целиком | ХранилищеЗначения |
| По значениям будут запросы и отчёты | Табличная часть / регистр |
| Строки с несколькими полями и связями | Табличная часть / регистр сведений |
| Временный набор на время расчёта | Коллекция в памяти, в базу не пишем |
Производительность: где незаметно теряется скорость
Коллекции прощают мелкие объёмы и жестоко наказывают за большие. Несколько мест, где чаще всего проседает скорость.
- 🔍 Поиск перебором.
Массив.НайтииТаблицаЗначений.НайтиСтрокибез индекса — это проход по всем элементам. На сотнях тысяч строк в цикле получаете квадрат. Решение:Соответствиедля поиска по ключу,Индексы.Добавитьдля таблицы. - ➕ Поэлементное добавление в большом цикле. Само по себе
Добавитьдёшево, дорого — то, что внутри: обращение к базе на каждой итерации. Собирайте данные одним запросом и выгружайте результат в таблицу черезВыгрузить(). - 🗜️ Крупные
ХранилищеЗначения. Большой упакованный объём читается и распаковывается целиком при каждом обращении к реквизиту. Если набор растёт — переводите в регистр. - 🔁 Изменение коллекции внутри
Для Каждого. Удаление элементов прямо в цикле обхода ломает перебор (см. ниже).
Перед оптимизацией замерьте. Включите замер производительности в конфигураторе или выведите ТекущаяУниверсальнаяДатаВМиллисекундах() до и после участка — часто «тормозит коллекция» оказывается запросом в цикле рядом с ней.
Что чаще всего ломается
Ошибки по этой теме повторяются из проекта в проект.
Попытка задать реквизиту тип «массив». Разработчик ищет такой тип в конструкторе и не находит, потому что его нет. Либо коллекция в памяти, либо ХранилищеЗначения, либо отдельная таблица.
Передача ТаблицаЗначений с клиента на сервер. Вызов падает, потому что таблица не сериализуется для такой передачи. Передавайте адрес во временном хранилище (ПоместитьВоВременноеХранилище) или массив структур.
Удаление элементов в Для Каждого. При удалении внутри обхода часть элементов проскакивается. Безопасный приём — собрать новую коллекцию:
Отфильтрованный = Новый Массив;
Для Каждого Элемент Из Исходный Цикл
Если НЕ НужноУдалить(Элемент) Тогда
Отфильтрованный.Добавить(Элемент);
КонецЦикла;
КонецЦикла;
Исходный = Отфильтрованный;
Если удалять нужно именно на месте, идите с конца индексом в цикле Пока:
Инд = Массив.Количество() - 1;
Пока Инд >= 0 Цикл
Если НужноУдалить(Массив[Инд]) Тогда
Массив.Удалить(Инд);
КонецЕсли;
Инд = Инд - 1;
КонецЦикла;
Коллекция передаётся по ссылке. Если передать массив или таблицу в процедуру и там его изменить, изменится и исходная коллекция — это один и тот же объект, а не копия. Когда нужна независимая копия таблицы, явно вызывайте ТЗ.Скопировать().
Ссылки в ХранилищеЗначения, которые потом удалили. Если вы упаковали в хранилище ссылки на справочник, а объекты позже пометили на удаление, при распаковке получите «битые» ссылки. Для долгого хранения кладите не ссылки, а их строковые идентификаторы или значимые реквизиты.
Пример: храним историю изменений документа
Задача — при каждой записи документа дописывать строку в журнал изменений (кто, когда, что менял). Покажу компактный вариант через ТаблицаЗначений в реквизите-хранилище; в конце — когда так делать не стоит.
// Реквизит документа ИсторияИзменений имеет тип ХранилищеЗначения
Процедура ДописатьВИсторию(ТекстИзменения) Экспорт
// достаём накопленную историю или создаём пустую
Если ТипЗнч(ИсторияИзменений) = Тип("ХранилищеЗначения") Тогда
Журнал = ИсторияИзменений.Получить();
Иначе
Журнал = Неопределено;
КонецЕсли;
Если ТипЗнч(Журнал) <> Тип("ТаблицаЗначений") Тогда
Журнал = Новый ТаблицаЗначений;
Журнал.Колонки.Добавить("Дата", Новый ОписаниеТипов("Дата"));
Журнал.Колонки.Добавить("Пользователь", Новый ОписаниеТипов("Строка"));
Журнал.Колонки.Добавить("Изменение", Новый ОписаниеТипов("Строка"));
КонецЕсли;
Строка = Журнал.Добавить();
Строка.Дата = ТекущаяДата();
Строка.Пользователь = Строка(ПользователиИнформационнойБазы.ТекущийПользователь());
Строка.Изменение = ТекстИзменения;
// упаковываем обратно со сжатием
ИсторияИзменений = Новый ХранилищеЗначения(Журнал, Новый СжатиеДанных(9));
КонецПроцедуры
Код решает задачу целиком: читает прошлую историю, при первом вызове создаёт таблицу с колонками, добавляет строку и пакует обратно. Перед записью документа не забудьте, что реквизит меняется — объект нужно записать.
Если история будет расти и по ней когда-нибудь понадобятся отчёты (кто чаще правит, изменения за период), правильнее не хранилище, а регистр сведений с измерениями «Документ», «Дата», «Пользователь». Хранилище — для случаев, когда журнал читается только целиком и редко.
Частые вопросы
Можно ли выбрать для реквизита тип «массив» или «список»?
ХранилищеЗначения или в отдельной таблице (табличная часть, регистр).Чем `СписокЗначений` лучше массива?
Почему падает передача `ТаблицаЗначений` на сервер?
ПоместитьВоВременноеХранилище или массив структур, а таблицу собирайте на сервере.Можно ли искать запросом по данным в `ХранилищеЗначения`?
Как ускорить поиск в большой коллекции?
Соответствие вместо Массив.Найти. Для таблицы добавьте индекс ТЗ.Индексы.Добавить(ИмяКолонки) — тогда НайтиСтроки работает быстрее перебора.