При разработке отчетов и обработок разработчики периодически сталкиваются с задачей: на форме документа отображается таблица с данными, заполненная оператором, и требуется сделать выборку документов, в которых эта таблица заполнена, а также извлечь сами строки. Однако попытка обратиться к такой таблице через язык запросов 1С приводит в тупик. Разберем архитектурные особенности платформы 1С, выясним причину такого поведения и рассмотрим, как правильно решить эту задачу.
Проанализируем ситуацию: запрос в 1С выполняется исключительно к таблицам базы данных (табличным частям, реквизитам объектов, регистрам). Элемент управления на форме привязан к реквизиту формы с типом ТаблицаЗначений. Реквизит формы представляет собой временный объект в оперативной памяти, существующий только в контексте открытого клиентского сеанса и окна формы. В самой базе данных такого понятия, как «реквизит формы», не существует, поэтому прямое обращение к нему через текст запроса невозможно.
Если при открытии существующего документа оператор видит сохраненные ранее строки, это означает лишь одно: при открытии формы срабатывает программный код, который считывает данные из какого-либо физического хранилища в базе и заполняет реквизит формы. Соответственно, при записи документа выполняется обратная операция.
Чтобы организовать выборку, сначала определим, откуда именно загружаются данные. Разберем по шагам, как локализовать место хранения:
ПриЧтенииНаСервере, ПриСозданииНаСервере (для управляемых форм) или ПриОткрытии (для обычных форм). В этих процедурах находится вызов алгоритма заполнения таблицы формы.ПередЗаписьюНаСервере, ПриЗаписиНаСервере в модуле формы, а также события ПередЗаписью и ПриЗаписи в модуле самого объекта документа. Здесь находится код сохранения содержимого таблицы значений в базу данных.На практике разработчики 1С чаще всего используют три способа сохранения нестандартных таблиц, не являющихся штатными табличными частями документа. Рассмотрим каждый из них и способы выборки данных.
Нередко таблица значений помещается в специальный реквизит документа с типом ХранилищеЗначений. В момент записи таблица сжимается и сохраняется в бинарном виде, а при чтении — извлекается методом Получить().
Ограничение платформы: реквизиты с типом ХранилищеЗначений нельзя фильтровать или анализировать непосредственно в теле запроса. Проверка на NULL или НЕ NULL в запросе не дает гарантии, так как внутри хранилища может быть записана пустая таблица значений (с нулевым количеством строк).
Для выборки таких документов используем комбинированный подход: отбираем документы запросом, а затем проверяем содержимое хранилища программно в цикле обхода выборки. Посмотрим на пример реализации:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Док.Ссылка КАК Ссылка,
| Док.ХранилищеТаблицы КАК ХранилищеТаблицы
|ИЗ
| Документ.РеализацияТоваровУслуг КАК Док
|ГДЕ
| Док.Проведен = ИСТИНА";
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
// Извлекаем значение напрямую из выборки без получения объекта в память
Таблица = Выборка.ХранилищеТаблицы.Получить();
Если ТипЗнч(Таблица) = Тип("ТаблицаЗначений") И Таблица.Количество() > 0 Тогда
// Документ содержит заполненную таблицу, обрабатываем строки
Для Каждого Стр Из Таблица Цикл
// Обработка данных строки: Стр.ИмяКолонки
КонецЦикла;
КонецЕсли;
КонецЦикла;
Обратим внимание: обращение к реквизиту через поле выборки запроса позволяет избежать тяжелой операции Ссылка.ПолучитьОбъект(), что существенно экономит оперативную память и ускоряет обработку большого массива документов.
Второй распространенный способ — использование независимого или подчиненного регистра сведений. В этом случае документ выступает либо регистратором, либо ссылка на него записывается в одно из измерений регистра.
Это наиболее производительный вариант, поскольку он позволяет получить и документы, и строки таблицы за один запрос с помощью соединения таблиц. Рассмотрим пример запроса, если документ связан с регистром по измерению Документ:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Док.Ссылка КАК ДокументСсылка,
| Док.Номер КАК Номер,
| Док.Дата КАК Дата,
| Рег.Номенклатура КАК Номенклатура,
| Рег.Количество КАК Количество,
| Рег.Цена КАК Цена
|ИЗ
| Документ.РеализацияТоваровУслуг КАК Док
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ДополнительныеДанныеДокументов КАК Рег
| ПО Док.Ссылка = Рег.Документ";
РезультатЗапроса = Запрос.Выполнить();
Использование конструкции ВНУТРЕННЕЕ СОЕДИНЕНИЕ автоматически исключает из результата запроса те документы, по которым в регистре сведений нет ни одной записи.
Иногда таблицу сериализуют в строку неограниченной длины методами ЗначениеВXML(), ЗначениеВСтрокуВнутр() или формируют JSON-структуру и записывают в текстовый реквизит документа. Строки неограниченной длины также не подлежат детальному разбору средствами языка запросов, поэтому их извлечение выполняется аналогично варианту с хранилищем: мы выбираем строковое поле запросом и программно восстанавливаем таблицу значений (например, через ЗначениеИзXML()) для последующей проверки на заполненность.