ТаблицаЗначений внутри ХранилищеЗначения: запись в реквизит, чтение, очистка и подводные камни
Содержание
Чтобы сохранить таблицу значений в хранилище 1С, оберните её конструктором Новый ХранилищеЗначения(Таблица, Новый СжатиеДанных(9)) и присвойте результат реквизиту объекта с типом ХранилищеЗначения, после чего запишите объект. Обратно таблицу достают методом .Получить(). Всё это выполняется на сервере: на тонком клиенте типа ТаблицаЗначений нет.
Если таблицу нужно хранить постоянно, в базе, — подходит реквизит или ресурс регистра типа ХранилищеЗначения. Если только передать между вызовами сервера в пределах открытой формы — проще временное хранилище. Если это настройка пользователя — хранилище общих настроек. Ниже разобраны все три варианта, но основной — первый.
Как устроен объект ХранилищеЗначения
ХранилищеЗначения — это контейнер. Он берёт любое сериализуемое значение, превращает его во внутренний двоичный формат и в таком виде лежит в поле базы данных. Внутрь можно положить таблицу значений, структуру, массив, табличный документ, картинку, двоичные данные.
Что важно знать до начала работы:
- 📦 Содержимое непрозрачно для СУБД. Отбирать, сортировать и группировать по полю типа
ХранилищеЗначенияв запросе нельзя. Только выбрать целиком. - 🔗 Ссылки хранятся как ссылки. Если в колонке
Номенклатуралежат элементы справочника, после извлечения это снова ссылки. Удалите элемент — в таблице останется битая ссылка<Объект не найден>. - 🧩 Типы колонок сохраняются. Колонки с их
ОписаниеТиповприходят обратно в том же виде, пересоздавать структуру не нужно. - 🗜️ Сжатие опционально. Второй параметр конструктора принимает объект
СжатиеДанныхс уровнем от 0 до 9. Для таблиц со множеством повторяющихся строк выигрыш в размере ощутимый. - 🚫 Не всё сериализуется. Например,
ЗапросиМенеджерВременныхТаблицположить в хранилище не получится — конструктор выдаст ошибку.
Где применяют такое хранение на практике:
| Задача | Почему хранилище, а не табличная часть |
|---|---|
| Снимок данных на дату (протокол расчёта, результат подбора) | Структура колонок может меняться, а снимок нужен «как есть» |
| Настройки обработки с произвольным набором колонок | Не нужно заводить метаданные под каждую колонку |
| Кэш тяжёлого запроса | Быстрее прочитать готовую таблицу, чем считать заново |
| Вложения: файлы, картинки, табличные документы | Двоичные данные удобно хранить в одном поле |
Если по данным нужно строить отчёты и отборы, хранилище — плохой выбор. Тогда нужна табличная часть или регистр сведений с нормальными полями.
Подготовка: реквизит типа ХранилищеЗначения в конфигураторе
Сначала нужно место для хранения. В типовой конфигурации на поддержке правильнее сделать это в расширении, чтобы не снимать объект с поддержки.
- Откройте конфигуратор, найдите в дереве метаданных нужный справочник, документ или регистр сведений.
- Добавьте реквизит (для регистра — ресурс), например
ДанныеТаблицы. - В свойстве «Тип» выберите
ХранилищеЗначения. - Обновите конфигурацию базы данных.
Для тяжёлых таблиц заведите отдельный регистр сведений: измерение — ссылка на объект, ресурс — ХранилищеЗначения. Тогда при чтении самого справочника или документа объёмные данные не тянутся вместе с ним, а в форме списка ничего не тормозит.
Такой регистр удобен ещё и тем, что ему можно задать отдельные права. Об этом — в разделе о безопасности.
Пошаговое сохранение таблицы значений в хранилище
Шаг 1. Получите саму таблицу на сервере
Таблица значений существует только на сервере и в толстом клиенте. Если она пришла из запроса, это уже ТаблицаЗначений:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Товары.Номенклатура КАК Номенклатура,
| Товары.Количество КАК Количество,
| Товары.Сумма КАК Сумма
|ИЗ
| Документ.РеализацияТоваровУслуг.Товары КАК Товары
|ГДЕ
| Товары.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", ДокументСсылка);
Таблица = Запрос.Выполнить().Выгрузить();
Если таблица лежит в реквизите управляемой формы, на клиенте это ДанныеФормыКоллекция, а не таблица значений. Превратите её в таблицу на сервере:
Таблица = РеквизитФормыВЗначение("ТаблицаТоваров");
Шаг 2. Упакуйте таблицу в хранилище и запишите объект
&НаСервереБезКонтекста
Процедура СохранитьТаблицу(Ссылка, Таблица)
Объект = Ссылка.ПолучитьОбъект();
Объект.ДанныеТаблицы = Новый ХранилищеЗначения(Таблица, Новый СжатиеДанных(9));
Объект.Записать();
КонецПроцедуры
Без Объект.Записать() значение останется только в памяти. Для регистра сведений порядок тот же, только вместо объекта — менеджер записи:
Запись = РегистрыСведений.ДанныеОбъектов.СоздатьМенеджерЗаписи();
Запись.Объект = Ссылка;
Запись.Данные = Новый ХранилищеЗначения(Таблица, Новый СжатиеДанных(9));
Запись.Записать(Истина); // Истина — заменить существующую запись
Шаг 3. Если пишете из формы самого объекта
Это главный подводный камень. Реквизит типа ХранилищеЗначения не попадает в данные формы: в Объект на форме его нет, и присвоить ему значение с клиента нельзя.
Рабочая схема — промежуточный реквизит формы и два обработчика:
&НаСервере
Процедура ПриЧтенииНаСервере(ТекущийОбъект)
Таблица = ТекущийОбъект.ДанныеТаблицы.Получить();
Если ТипЗнч(Таблица) = Тип("ТаблицаЗначений") Тогда
ЗначениеВРеквизитФормы(Таблица, "ТаблицаТоваров");
КонецЕсли;
КонецПроцедуры
&НаСервере
Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
Таблица = РеквизитФормыВЗначение("ТаблицаТоваров");
ТекущийОбъект.ДанныеТаблицы = Новый ХранилищеЗначения(Таблица, Новый СжатиеДанных(9));
КонецПроцедуры
Пользователь редактирует ТаблицаТоваров на форме как обычную таблицу. При записи платформа сама вызывает ПередЗаписьюНаСервере, и данные уходят в хранилище вместе с остальными реквизитами. Записать() здесь вызывать не нужно — запись уже идёт.
Колонки реквизита формы ТаблицаТоваров должны совпадать с колонками сохранённой таблицы. Иначе ЗначениеВРеквизитФормы перенесёт только совпавшие колонки, остальные данные на форме пропадут, а при следующей записи затрутся.
Извлечение и восстановление таблицы из хранилища
Чтение через объект
Таблица = Ссылка.ПолучитьОбъект().ДанныеТаблицы.Получить();
Короткий вариант Ссылка.ДанныеТаблицы.Получить() тоже работает, но обращение через точку читает данные объекта целиком. Если объект большой, а нужна только таблица, лучше запрос.
Чтение запросом
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Спр.ДанныеТаблицы КАК ДанныеТаблицы
|ИЗ
| Справочник.Сценарии КАК Спр
|ГДЕ
| Спр.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", Ссылка);
Выборка = Запрос.Выполнить().Выбрать();
Таблица = Неопределено;
Если Выборка.Следующий() Тогда
Таблица = Выборка.ДанныеТаблицы.Получить();
КонецЕсли;
Если ТипЗнч(Таблица) <> Тип("ТаблицаЗначений") Тогда
Таблица = Новый ТаблицаЗначений; // пустая заготовка, чтобы код дальше не упал
КонецЕсли;
Поле в выборке имеет тип ХранилищеЗначения, поэтому .Получить() обязателен. Отбор по Ссылка в ГДЕ допустим — нельзя только условие по самому полю хранилища.
Проверка после извлечения
Проверка ТипЗнч в примере выше не формальность. .Получить() вернёт Неопределено, если реквизит ни разу не заполняли, а в старых записях может лежать значение другого типа. Без проверки код упадёт на первом обращении к колонке.
Если структура колонок со временем менялась, проверьте и её:
Если Таблица.Колонки.Найти("Сумма") = Неопределено Тогда
Таблица.Колонки.Добавить("Сумма", Новый ОписаниеТипов("Число"));
КонецЕсли;
Новая колонка добавится в старые данные с пустыми значениями, и дальнейший код не сломается.
Временное хранилище и хранилище настроек: когда они удобнее
Под словом «хранилище» в 1С понимают три разных механизма. Выбор зависит от того, сколько должны жить данные.
| Механизм | Сколько живут данные | Типичный сценарий |
|---|---|---|
Реквизит типа ХранилищеЗначения | Постоянно, в базе | Снимок, протокол, вложение |
| Временное хранилище | До закрытия формы или в пределах вызова сервера | Передать таблицу с сервера на клиент и обратно |
| Хранилище общих настроек | Постоянно, привязано к пользователю | Сохранённые колонки, фильтры, черновики |
Временное хранилище
Нужно, когда таблицу надо «пронести» через клиент. Клиент не может держать ТаблицаЗначений, зато держит строку-адрес:
&НаСервере
Функция ПодготовитьТаблицу()
Таблица = ПолучитьТаблицуНаСервере(); // ваша функция
Возврат ПоместитьВоВременноеХранилище(Таблица, УникальныйИдентификатор);
КонецФункции
Передан УникальныйИдентификатор формы — данные живут, пока форма открыта. Без него они быстро удаляются, после окончания вызова сервера. Обратно данные берут через ПолучитьИзВременногоХранилища(Адрес), а освобождают память через УдалитьИзВременногоХранилища(Адрес).
Хранилище общих настроек
ХранилищеОбщихНастроек.Сохранить("МояОбработка", "ПоследнийПодбор", Таблица);
Таблица = ХранилищеОбщихНастроек.Загрузить("МояОбработка", "ПоследнийПодбор");
Первый параметр — ключ объекта, второй — ключ настройки. Настройка сохраняется для текущего пользователя. В конфигурациях на БСП используйте обёртки модуля ОбщегоНазначения: ХранилищеОбщихНастроекСохранить и ХранилищеОбщихНастроекЗагрузить — они учитывают права и не роняют код, если сохранить настройку нельзя.
Типизация и безопасность данных в хранилище
Что с типами
Хранилище не проверяет, что внутри. Сегодня в ДанныеТаблицы пишут таблицу, завтра коллега запишет туда структуру — и платформа промолчит. Защита одна: проверять ТипЗнч при чтении и держать запись в одной процедуре, а не разбрасывать по коду.
Пара практических правил:
- 🧱 Задавайте типы колонок явно:
Таблица.Колонки.Добавить("Сумма", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2))). Колонка без типа примет что угодно, и после извлечения в ней может оказаться строка вместо числа. - 🏷️ Если формат сохранённых данных меняется, кладите в хранилище структуру с версией:
Новый Структура("Версия, Таблица", 2, Таблица). При чтении сразу понятно, нужна ли конвертация.
Что с безопасностью
Хранилище — это не шифрование. Данные лежат в базе во внутреннем формате, и любой, у кого есть право чтения объекта, достанет их запросом или внешней обработкой.
Отсюда выводы:
- 🔒 Пароли, токены и ключи в обычное хранилище не кладите. В конфигурациях на БСП для этого есть безопасное хранилище:
ОбщегоНазначения.ЗаписатьДанныеВБезопасноеХранилищеиОбщегоНазначения.ПрочитатьДанныеИзБезопасногоХранилища. Данные там лежат в отдельном регистре, закрытом для обычных пользователей. - 👥 Если в хранилище лежит что-то конфиденциальное (например, расчёт с суммами по сотрудникам), вынесите его в отдельный регистр сведений и выдайте права на него только нужной роли.
Очистка хранилища и экономия памяти
Как очистить значение в базе
Пустое хранилище создают без значения, затем записывают объект:
Объект = Ссылка.ПолучитьОбъект();
Объект.ДанныеТаблицы = Новый ХранилищеЗначения(Неопределено);
Объект.Записать();
Для отдельного регистра сведений проще удалить саму запись — через набор записей с отбором по измерению.
Как не раздувать базу и память
- 🗜️ Сжимайте.
Новый СжатиеДанных(9)даёт наименьший размер ценой небольшой нагрузки на процессор при записи. Для данных, которые пишут редко, а хранят долго, это разумный обмен. - ✂️ Удаляйте лишние колонки перед сохранением:
Таблица.Колонки.Удалить("Служебная"). Особенно колонки с представлениями — их можно восстановить по ссылке. - 🧹 Освобождайте переменные. После обработки большой таблицы присвойте переменной
Неопределено. Иначе в длинной процедуре или цикле по объектам память не освобождается до выхода. - 🔁 Не извлекайте в цикле повторно. Достали таблицу один раз — работайте с переменной. Каждый
.Получить()распаковывает данные заново. - 🗂️ Чистите устаревшие снимки регламентным заданием. Хранилища со старыми протоколами — частая причина того, что база растёт без видимых причин.
Чтобы найти, какие объекты раздувают базу, сравните размеры таблиц на стороне СУБД. Таблица объекта с реквизитом-хранилищем, который пишут на каждую запись, обычно сразу видна в верхней части списка.
Ошибки при работе с хранилищем и как их исправить
Частые вопросы
Сколько данных можно положить в одно хранилище?
Можно ли сохранить таблицу значений во внешней обработке без доработки конфигурации?
ЗначениеВСтрокуВнутр и запишите строку текстовым документом, обратно — ЗначениеИзСтрокиВнутр.Перенесётся ли содержимое хранилища при выгрузке базы в .dt?
Можно ли положить в хранилище дерево значений вместо таблицы?
Тип("ДеревоЗначений").