ТаблицаЗначений внутри ХранилищеЗначения: запись в реквизит, чтение, очистка и подводные камни

· 9 мин

Содержание

Чтобы сохранить таблицу значений в хранилище 1С, оберните её конструктором Новый ХранилищеЗначения(Таблица, Новый СжатиеДанных(9)) и присвойте результат реквизиту объекта с типом ХранилищеЗначения, после чего запишите объект. Обратно таблицу достают методом .Получить(). Всё это выполняется на сервере: на тонком клиенте типа ТаблицаЗначений нет.

Если таблицу нужно хранить постоянно, в базе, — подходит реквизит или ресурс регистра типа ХранилищеЗначения. Если только передать между вызовами сервера в пределах открытой формы — проще временное хранилище. Если это настройка пользователя — хранилище общих настроек. Ниже разобраны все три варианта, но основной — первый.

Как устроен объект ХранилищеЗначения

ХранилищеЗначения — это контейнер. Он берёт любое сериализуемое значение, превращает его во внутренний двоичный формат и в таком виде лежит в поле базы данных. Внутрь можно положить таблицу значений, структуру, массив, табличный документ, картинку, двоичные данные.

Что важно знать до начала работы:

  • 📦 Содержимое непрозрачно для СУБД. Отбирать, сортировать и группировать по полю типа ХранилищеЗначения в запросе нельзя. Только выбрать целиком.
  • 🔗 Ссылки хранятся как ссылки. Если в колонке Номенклатура лежат элементы справочника, после извлечения это снова ссылки. Удалите элемент — в таблице останется битая ссылка <Объект не найден>.
  • 🧩 Типы колонок сохраняются. Колонки с их ОписаниеТипов приходят обратно в том же виде, пересоздавать структуру не нужно.
  • 🗜️ Сжатие опционально. Второй параметр конструктора принимает объект СжатиеДанных с уровнем от 0 до 9. Для таблиц со множеством повторяющихся строк выигрыш в размере ощутимый.
  • 🚫 Не всё сериализуется. Например, Запрос и МенеджерВременныхТаблиц положить в хранилище не получится — конструктор выдаст ошибку.

Где применяют такое хранение на практике:

ЗадачаПочему хранилище, а не табличная часть
Снимок данных на дату (протокол расчёта, результат подбора)Структура колонок может меняться, а снимок нужен «как есть»
Настройки обработки с произвольным набором колонокНе нужно заводить метаданные под каждую колонку
Кэш тяжёлого запросаБыстрее прочитать готовую таблицу, чем считать заново
Вложения: файлы, картинки, табличные документыДвоичные данные удобно хранить в одном поле

Если по данным нужно строить отчёты и отборы, хранилище — плохой выбор. Тогда нужна табличная часть или регистр сведений с нормальными полями.

Подготовка: реквизит типа ХранилищеЗначения в конфигураторе

Сначала нужно место для хранения. В типовой конфигурации на поддержке правильнее сделать это в расширении, чтобы не снимать объект с поддержки.

  1. Откройте конфигуратор, найдите в дереве метаданных нужный справочник, документ или регистр сведений.
  2. Добавьте реквизит (для регистра — ресурс), например ДанныеТаблицы.
  3. В свойстве «Тип» выберите ХранилищеЗначения.
  4. Обновите конфигурацию базы данных.

Для тяжёлых таблиц заведите отдельный регистр сведений: измерение — ссылка на объект, ресурс — ХранилищеЗначения. Тогда при чтении самого справочника или документа объёмные данные не тянутся вместе с ним, а в форме списка ничего не тормозит.

Такой регистр удобен ещё и тем, что ему можно задать отдельные права. Об этом — в разделе о безопасности.

Пошаговое сохранение таблицы значений в хранилище

Шаг 1. Получите саму таблицу на сервере

Таблица значений существует только на сервере и в толстом клиенте. Если она пришла из запроса, это уже ТаблицаЗначений:

Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
|	Товары.Номенклатура КАК Номенклатура,
|	Товары.Количество КАК Количество,
|	Товары.Сумма КАК Сумма
|ИЗ
|	Документ.РеализацияТоваровУслуг.Товары КАК Товары
|ГДЕ
|	Товары.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", ДокументСсылка);
Таблица = Запрос.Выполнить().Выгрузить();

Если таблица лежит в реквизите управляемой формы, на клиенте это ДанныеФормыКоллекция, а не таблица значений. Превратите её в таблицу на сервере:

Таблица = РеквизитФормыВЗначение("ТаблицаТоваров");

Шаг 2. Упакуйте таблицу в хранилище и запишите объект

&НаСервереБезКонтекста
Процедура СохранитьТаблицу(Ссылка, Таблица)

	Объект = Ссылка.ПолучитьОбъект();
	Объект.ДанныеТаблицы = Новый ХранилищеЗначения(Таблица, Новый СжатиеДанных(9));
	Объект.Записать();

КонецПроцедуры

Без Объект.Записать() значение останется только в памяти. Для регистра сведений порядок тот же, только вместо объекта — менеджер записи:

Запись = РегистрыСведений.ДанныеОбъектов.СоздатьМенеджерЗаписи();
Запись.Объект = Ссылка;
Запись.Данные = Новый ХранилищеЗначения(Таблица, Новый СжатиеДанных(9));
Запись.Записать(Истина); // Истина — заменить существующую запись

Шаг 3. Если пишете из формы самого объекта

Это главный подводный камень. Реквизит типа ХранилищеЗначения не попадает в данные формы: в Объект на форме его нет, и присвоить ему значение с клиента нельзя.

Рабочая схема — промежуточный реквизит формы и два обработчика:

&НаСервере
Процедура ПриЧтенииНаСервере(ТекущийОбъект)

	Таблица = ТекущийОбъект.ДанныеТаблицы.Получить();
	Если ТипЗнч(Таблица) = Тип("ТаблицаЗначений") Тогда
		ЗначениеВРеквизитФормы(Таблица, "ТаблицаТоваров");
	КонецЕсли;

КонецПроцедуры

&НаСервере
Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)

	Таблица = РеквизитФормыВЗначение("ТаблицаТоваров");
	ТекущийОбъект.ДанныеТаблицы = Новый ХранилищеЗначения(Таблица, Новый СжатиеДанных(9));

КонецПроцедуры

Пользователь редактирует ТаблицаТоваров на форме как обычную таблицу. При записи платформа сама вызывает ПередЗаписьюНаСервере, и данные уходят в хранилище вместе с остальными реквизитами. Записать() здесь вызывать не нужно — запись уже идёт.

Колонки реквизита формы ТаблицаТоваров должны совпадать с колонками сохранённой таблицы. Иначе ЗначениеВРеквизитФормы перенесёт только совпавшие колонки, остальные данные на форме пропадут, а при следующей записи затрутся.

Извлечение и восстановление таблицы из хранилища

Чтение через объект

Таблица = Ссылка.ПолучитьОбъект().ДанныеТаблицы.Получить();

Короткий вариант Ссылка.ДанныеТаблицы.Получить() тоже работает, но обращение через точку читает данные объекта целиком. Если объект большой, а нужна только таблица, лучше запрос.

Чтение запросом

Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
|	Спр.ДанныеТаблицы КАК ДанныеТаблицы
|ИЗ
|	Справочник.Сценарии КАК Спр
|ГДЕ
|	Спр.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", Ссылка);
Выборка = Запрос.Выполнить().Выбрать();

Таблица = Неопределено;
Если Выборка.Следующий() Тогда
	Таблица = Выборка.ДанныеТаблицы.Получить();
КонецЕсли;

Если ТипЗнч(Таблица) <> Тип("ТаблицаЗначений") Тогда
	Таблица = Новый ТаблицаЗначений; // пустая заготовка, чтобы код дальше не упал
КонецЕсли;

Поле в выборке имеет тип ХранилищеЗначения, поэтому .Получить() обязателен. Отбор по Ссылка в ГДЕ допустим — нельзя только условие по самому полю хранилища.

Проверка после извлечения

Проверка ТипЗнч в примере выше не формальность. .Получить() вернёт Неопределено, если реквизит ни разу не заполняли, а в старых записях может лежать значение другого типа. Без проверки код упадёт на первом обращении к колонке.

Если структура колонок со временем менялась, проверьте и её:

Если Таблица.Колонки.Найти("Сумма") = Неопределено Тогда
	Таблица.Колонки.Добавить("Сумма", Новый ОписаниеТипов("Число"));
КонецЕсли;

Новая колонка добавится в старые данные с пустыми значениями, и дальнейший код не сломается.

Временное хранилище и хранилище настроек: когда они удобнее

Под словом «хранилище» в 1С понимают три разных механизма. Выбор зависит от того, сколько должны жить данные.

МеханизмСколько живут данныеТипичный сценарий
Реквизит типа ХранилищеЗначенияПостоянно, в базеСнимок, протокол, вложение
Временное хранилищеДо закрытия формы или в пределах вызова сервераПередать таблицу с сервера на клиент и обратно
Хранилище общих настроекПостоянно, привязано к пользователюСохранённые колонки, фильтры, черновики

Временное хранилище

Нужно, когда таблицу надо «пронести» через клиент. Клиент не может держать ТаблицаЗначений, зато держит строку-адрес:

&НаСервере
Функция ПодготовитьТаблицу()

	Таблица = ПолучитьТаблицуНаСервере(); // ваша функция
	Возврат ПоместитьВоВременноеХранилище(Таблица, УникальныйИдентификатор);

КонецФункции

Передан УникальныйИдентификатор формы — данные живут, пока форма открыта. Без него они быстро удаляются, после окончания вызова сервера. Обратно данные берут через ПолучитьИзВременногоХранилища(Адрес), а освобождают память через УдалитьИзВременногоХранилища(Адрес).

Хранилище общих настроек

ХранилищеОбщихНастроек.Сохранить("МояОбработка", "ПоследнийПодбор", Таблица);
Таблица = ХранилищеОбщихНастроек.Загрузить("МояОбработка", "ПоследнийПодбор");

Первый параметр — ключ объекта, второй — ключ настройки. Настройка сохраняется для текущего пользователя. В конфигурациях на БСП используйте обёртки модуля ОбщегоНазначения: ХранилищеОбщихНастроекСохранить и ХранилищеОбщихНастроекЗагрузить — они учитывают права и не роняют код, если сохранить настройку нельзя.

Типизация и безопасность данных в хранилище

Что с типами

Хранилище не проверяет, что внутри. Сегодня в ДанныеТаблицы пишут таблицу, завтра коллега запишет туда структуру — и платформа промолчит. Защита одна: проверять ТипЗнч при чтении и держать запись в одной процедуре, а не разбрасывать по коду.

Пара практических правил:

  • 🧱 Задавайте типы колонок явно: Таблица.Колонки.Добавить("Сумма", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2))). Колонка без типа примет что угодно, и после извлечения в ней может оказаться строка вместо числа.
  • 🏷️ Если формат сохранённых данных меняется, кладите в хранилище структуру с версией: Новый Структура("Версия, Таблица", 2, Таблица). При чтении сразу понятно, нужна ли конвертация.

Что с безопасностью

Хранилище — это не шифрование. Данные лежат в базе во внутреннем формате, и любой, у кого есть право чтения объекта, достанет их запросом или внешней обработкой.

Отсюда выводы:

  • 🔒 Пароли, токены и ключи в обычное хранилище не кладите. В конфигурациях на БСП для этого есть безопасное хранилище: ОбщегоНазначения.ЗаписатьДанныеВБезопасноеХранилище и ОбщегоНазначения.ПрочитатьДанныеИзБезопасногоХранилища. Данные там лежат в отдельном регистре, закрытом для обычных пользователей.
  • 👥 Если в хранилище лежит что-то конфиденциальное (например, расчёт с суммами по сотрудникам), вынесите его в отдельный регистр сведений и выдайте права на него только нужной роли.

Очистка хранилища и экономия памяти

Как очистить значение в базе

Пустое хранилище создают без значения, затем записывают объект:

Объект = Ссылка.ПолучитьОбъект();
Объект.ДанныеТаблицы = Новый ХранилищеЗначения(Неопределено);
Объект.Записать();

Для отдельного регистра сведений проще удалить саму запись — через набор записей с отбором по измерению.

Как не раздувать базу и память

  • 🗜️ Сжимайте. Новый СжатиеДанных(9) даёт наименьший размер ценой небольшой нагрузки на процессор при записи. Для данных, которые пишут редко, а хранят долго, это разумный обмен.
  • ✂️ Удаляйте лишние колонки перед сохранением: Таблица.Колонки.Удалить("Служебная"). Особенно колонки с представлениями — их можно восстановить по ссылке.
  • 🧹 Освобождайте переменные. После обработки большой таблицы присвойте переменной Неопределено. Иначе в длинной процедуре или цикле по объектам память не освобождается до выхода.
  • 🔁 Не извлекайте в цикле повторно. Достали таблицу один раз — работайте с переменной. Каждый .Получить() распаковывает данные заново.
  • 🗂️ Чистите устаревшие снимки регламентным заданием. Хранилища со старыми протоколами — частая причина того, что база растёт без видимых причин.

Чтобы найти, какие объекты раздувают базу, сравните размеры таблиц на стороне СУБД. Таблица объекта с реквизитом-хранилищем, который пишут на каждую запись, обычно сразу видна в верхней части списка.

Ошибки при работе с хранилищем и как их исправить

Диагностика: выберите симптом

Частые вопросы

Сколько данных можно положить в одно хранилище?
Жёсткого маленького лимита нет, но большие объёмы замедляют чтение объекта и рост базы. Таблицы на сотни тысяч строк лучше хранить в регистре сведений с нормальными полями.
Можно ли сохранить таблицу значений во внешней обработке без доработки конфигурации?
Да: через хранилище общих настроек или в файл. Для файла сериализуйте таблицу через ЗначениеВСтрокуВнутр и запишите строку текстовым документом, обратно — ЗначениеИзСтрокиВнутр.
Перенесётся ли содержимое хранилища при выгрузке базы в .dt?
Да, это обычное поле базы, оно выгружается и загружается вместе с остальными данными.
Можно ли положить в хранилище дерево значений вместо таблицы?
Можно. ДеревоЗначений сериализуется так же, как таблица, а при чтении проверяйте тип Тип("ДеревоЗначений").
Чем ХранилищеЗначения лучше реквизита типа ДвоичныеДанные?
Для двоичных данных реквизита в базе такого типа нет: файлы и картинки тоже кладут в хранилище. Хранилище принимает и таблицы, и структуры, и двоичные данные, а при чтении возвращает исходный тип.