При работе с платформой 1С:Предприятие 8.3 программисты часто сталкиваются с необходимостью получить данные табличных частей документа или другого объекта по его ссылке — поможет разработка и оптимизация BSL-кода с помощью ИИ. Эта задача кажется простой, но имеет критически важные нюансы, касающиеся производительности и потребления системных ресурсов. Давайте вместе разберемся, как это делать правильно, избегая распространенных ошибок, и какие инструменты нам предлагает платформа.
Мы знаем, что получать реквизиты объекта по ссылке через точку, такие как ДокументСсылка.Организация, является неоптимальным подходом. Почему? Потому что в этом случае платформа вынуждена загружать весь объект целиком из базы данных, включая все его реквизиты и табличные части, даже если нам нужен всего лишь один небольшой реквизит. Это приводит к избыточной нагрузке на сеть, сервер и клиентский компьютер, значительно снижая производительность работы системы — для этого подойдёт поиск причин замедлений и анализ долгих запросов.
Возникают закономерные вопросы: распространяется ли это правило на табличные части? Что происходит, если мы обращаемся к ним напрямую через точку, например, ДокументСсылка.Товары или даже к их свойствам вроде ДокументСсылка.Товары.Количество? Давайте проанализируем эти ситуации.
Когда мы пытаемся получить табличную часть или ее свойства, используя синтаксис ДокументСсылка.Товары, платформа действует точно так же, как и при обращении к обычным реквизитам. То есть, она полностью загружает весь объект документа из базы данных в память. Это включает в себя все реквизиты шапки документа и, что особенно важно, все строки всех его табличных частей. Даже если нам нужно всего лишь узнать количество строк в табличной части Товары (ДокументСсылка.Товары.Количество) или найти определенные строки (ДокументСсылка.Товары.НайтиСтроки), платформа все равно вынуждена загрузить всю табличную часть, а затем весь объект.
Представьте, если у вас документ содержит несколько табличных частей, каждая из которых имеет сотни или тысячи строк — поможет анализ структуры и объема таблиц базы данных. Каждое такое обращение приведет к огромной нагрузке на систему. Это не только замедляет выполнение кода, но и значительно увеличивает потребление оперативной памяти, что может привести к проблемам с масштабируемостью и стабильностью работы.
Таким образом, прямой доступ к табличным частям по ссылке через точку является крайне неэффективным и должен быть исключен из нашей практики при программировании на 1С.
Чтобы лучше понять суть проблемы, давайте вспомним о ключевом различии между "ссылкой 1С на данные" и "объектом 1С".
Когда мы обращаемся к реквизиту по ссылке через точку (например, ДокументСсылка.Товары), платформа неявно преобразует ссылку в объект, чтобы получить доступ к его данным. Именно это неявное создание объекта и приводит к полной загрузке всех данных, включая табличные части.
Наиболее эффективным и рекомендованным способом получения табличных частей объектов по ссылке в 1С является использование запросов — для этого есть набор инструментов: консоль запросов и кода. Запрос позволяет нам:
Рассмотрим несколько примеров, как использовать запросы для получения табличных частей.
Допустим, нам нужна вся табличная часть "Товары" документа по его ссылке.
Функция ПолучитьТабличнуюЧастьТовары(СсылкаДокумента) Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ДокументТовары.Номенклатура,
| ДокументТовары.Количество,
| ДокументТовары.Цена,
| ДокументТовары.Сумма
|ИЗ
| Документ.РеализацияТоваровУслуг.Товары КАК ДокументТовары
|ГДЕ
| ДокументТовары.Ссылка = &СсылкаДокумента
|УПОРЯДОЧИТЬ ПО
| ДокументТовары.НомерСтроки";
Запрос.УстановитьПараметр("СсылкаДокумента", СсылкаДокумента);
РезультатЗапроса = Запрос.Выполнить();
Возврат РезультатЗапроса.Выгрузить(); // Возвращаем ТаблицуЗначений
КонецФункции
// Пример использования
// СсылкаНаРеализацию = Документы.РеализацияТоваровУслуг.НайтиПоНомеру("000000001", Дата("20230101"));
// Если СсылкаНаРеализацию.Пустая() Тогда
// Сообщить("Документ не найден!");
// Иначе
// ТабличнаяЧастьТовары = ПолучитьТабличнуюЧастьТовары(СсылкаНаРеализацию);
// Если ТабличнаяЧастьТовары.Количество() > 0 Тогда
// Сообщить("Получены строки табличной части: " + ТабличнаяЧастьТовары.Количество());
// // ТабличнаяЧастьТовары теперь содержит данные
// КонецЕсли;
// КонецЕсли;
В этом примере мы явно указываем, какие поля нам нужны из табличной части Товары документа РеализацияТоваровУслуг, и фильтруем по ссылке на конкретный документ. В результате мы получаем ТаблицуЗначений только с необходимыми данными, минуя полную загрузку объекта.
Часто нам нужны не просто ссылки на номенклатуру, а ее наименование, артикул и другие свойства.
Функция ПолучитьТабличнуюЧастьТоварыСДетализацией(СсылкаДокумента) Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ДокументТовары.Номенклатура КАК НоменклатураСсылка,
| ДокументТовары.Номенклатура.Наименование КАК НоменклатураНаименование,
| ДокументТовары.Номенклатура.Артикул КАК НоменклатураАртикул,
| ДокументТовары.Количество,
| ДокументТовары.Цена,
| ДокументТовары.Сумма
|ИЗ
| Документ.РеализацияТоваровУслуг.Товары КАК ДокументТовары
|ГДЕ
| ДокументТовары.Ссылка = &СсылкаДокумента
|УПОРЯДОЧИТЬ ПО
| ДокументТовары.НомерСтроки";
Запрос.УстановитьПараметр("СсылкаДокумента", СсылкаДокумента);
РезультатЗапроса = Запрос.Выполнить();
Возврат РезультатЗапроса.Выгрузить();
КонецФункции
Здесь мы используем точечную нотацию в запросе (ДокументТовары.Номенклатура.Наименование) для получения данных из справочника Номенклатура, связанных с каждой строкой табличной части. Это также выполняется эффективно, поскольку платформа 1С оптимизирует такие запросы.
Библиотека стандартных подсистем (БСП) предлагает полезные функции, которые могут помочь в некоторых случаях, хотя для табличных частей их использование требует понимания нюансов. Речь идет о функциях ОбщегоНазначения.ЗначениеРеквизитаОбъекта и ОбщегоНазначения.ЗначенияРеквизитовОбъекта.
Изначально эти функции предназначены для получения значений отдельных реквизитов объекта по ссылке, не загружая при этом весь объект целиком. Важно отметить, что внутри этих функций для чтения данных также используется запрос — есть архитектурный анализ конфигурации и аудит кода.
ОбщегоНазначения.ЗначениеРеквизитаОбъекта для табличной частиХотя эта функция не была специально "предназначена" для получения табличных частей в широком смысле, ее можно использовать для их получения в виде коллекции объектов (например, ТабличнаяЧастьОбъектаКоллекция), которую затем можно выгрузить в ТаблицуЗначений.
Функция ПолучитьТабличнуюЧастьБСП(СсылкаДокумента) Экспорт
// Получаем коллекцию строк табличной части
ТабличнаяЧастьКоллекция = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(СсылкаДокумента, "Товары");
// Выгружаем коллекцию в ТаблицуЗначений
Если ТабличнаяЧастьКоллекция <> Неопределено Тогда
Возврат ТабличнаяЧастьКоллекция.Выгрузить();
Иначе
Возврат Новый ТаблицаЗначений;
КонецЕсли;
КонецФункции
// Пример использования
// ТабличнаяЧастьТоварыБСП = ПолучитьТабличнуюЧастьБСП(СсылкаНаРеализацию);
// Если ТабличнаяЧастьТоварыБСП.Количество() > 0 Тогда
// Сообщить("Получены строки табличной части через БСП: " + ТабличнаяЧастьТоварыБСП.Количество());
// КонецЕсли;
Этот подход достаточно лаконичен. Он работает, потому что функция ЗначениеРеквизитаОбъекта, столкнувшись с запросом на получение табличной части, все равно выполняет запрос к базе данных. Однако, вместо того чтобы возвращать сразу ТаблицуЗначений, она возвращает коллекцию, которую нам необходимо выгрузить. Это позволяет избежать полной загрузки объекта документа, но при этом для самой табличной части все равно происходит выборка из БД.
ОбщегоНазначения.ЗначенияРеквизитовОбъекта для нескольких реквизитов, включая табличную частьЭта функция особенно удобна, когда нам нужно получить сразу несколько реквизитов объекта, среди которых есть и табличные части, и обычные реквизиты. Она возвращает структуру, где каждый ключ – это имя реквизита, а значение – соответствующее ему значение.
Функция ПолучитьРеквизитыИТабличнуюЧастьБСП(СсылкаДокумента) Экспорт
// Получаем несколько реквизитов и табличную часть за одно обращение
РеквизитыОбъекта = ОбщегоНазначения.ЗначенияРеквизитовОбъекта(СсылкаДокумента, "Организация,Склад,Товары");
Организация = РеквизитыОбъекта.Организация;
Склад = РеквизитыОбъекта.Склад;
// Табличную часть необходимо выгрузить
ТабличнаяЧастьТовары = Новый ТаблицаЗначений;
Если РеквизитыОбъекта.Свойство("Товары") И РеквизитыОбъекта.Товары <> Неопределено Тогда
ТабличнаяЧастьТовары = РеквизитыОбъекта.Товары.Выгрузить();
КонецЕсли;
Возврат Новый Структура("Организация,Склад,Товары", Организация, Склад, ТабличнаяЧастьТовары);
КонецФункции
// Пример использования
// Результат = ПолучитьРеквизитыИТабличнуюЧастьБСП(СсылкаНаРеализацию);
// Сообщить("Организация: " + Результат.Организация);
// Сообщить("Склад: " + Результат.Склад);
// Сообщить("Строк в ТЧ Товары: " + Результат.Товары.Количество());
В этом сценарии, когда нам требуется несколько реквизитов, подход с ОбщегоНазначения.ЗначенияРеквизитовОбъекта выглядит достаточно обоснованным. Он позволяет одним вызовом получить нужные данные, при этом платформа внутренне оптимизирует запрос для их выборки.
ЗначенияРеквизитовОбъекта позволяет одним вызовом получить и обычные реквизиты, и табличные части, что удобно для комплексных задач.ТаблицуЗначений, он будет потреблять оперативную память. Однако, как отмечают коллеги на форуме, это не сильно отличается от выгрузки результата обычного запроса.В 1С существует механизм кэширования объектов, который может ускорять повторное обращение к одним и тем же данным. Если объект уже был загружен в память, при следующем обращении к его реквизитам (через ссылку) данные могут быть взяты из кэша, что ускорит операцию. Однако это кэширование относится к свойствам самого объекта, а не к его табличным частям. При обращении к табличным частям объекта, даже если сам объект уже кэширован, всегда происходит дополнительная загрузка данных табличной части из базы данных, что нивелирует преимущества кэширования для этих данных.
Давайте подведем итоги и выработаем рекомендации по выбору метода для получения табличных частей:
Если вам нужны табличные части объекта, наиболее эффективным и рекомендованным методом является прямой запрос к базе данных. Запросы позволяют максимально точно выбрать необходимые данные, минимизировать объем передаваемой информации и выполнить необходимые объединения с другими таблицами за одно обращение к БД. Это обеспечивает наилучшую производительность и экономию ресурсов. Особенно это критично для документов с большим количеством строк.
Мы настоятельно рекомендуем использовать запросы, когда:
Функции ОбщегоНазначения.ЗначениеРеквизитаОбъекта и ОбщегоНазначения.ЗначенияРеквизитовОбъекта могут быть полезны, особенно когда требуется получить не только табличную часть, но и другие реквизиты объекта за один раз. Они обеспечивают лаконичный код и не приводят к полной загрузке всего объекта, так как внутри также используют запросы.
Рассмотрите использование функций БСП, когда:
Однако, всегда помните, что даже эти функции не избавляют от факта выборки данных табличной части из базы, и если нужна только одна большая табличная часть, специализированный запрос, скорее всего, будет работать быстрее.
Правильное обращение к табличным частям объектов по ссылке в 1С – это не просто вопрос "как работает", но и вопрос "как работает эффективно". Прямое обращение через точку к табличной части (например, ДокументСсылка.Товары) приводит к избыточной загрузке всего объекта из базы данных, что является крайне неэффективным. Для оптимальной работы мы должны использовать запросы, которые позволяют точно указать необходимые данные и минимизировать нагрузку на систему. В некоторых случаях, для удобства и лаконичности, можно применять функции БСП ОбщегоНазначения.ЗначениеРеквизитаОбъекта или ОбщегоНазначения.ЗначенияРеквизитовОбъекта, но с пониманием того, что они также выполняют внутренние запросы и могут быть менее гибкими или чуть менее производительными, чем специально написанный запрос для конкретной задачи. Выбирая метод, всегда ориентируемся на требования к производительности, объему данных и сложности задачи.