Как правильно и эффективно получать табличные части объектов по ссылке в 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При работе с платформой 1С:Предприятие 8.3 программисты часто сталкиваются с необходимостью получить данные табличных частей документа или другого объекта по его ссылке — поможет разработка и оптимизация BSL-кода с помощью ИИ. Эта задача кажется простой, но имеет критически важные нюансы, касающиеся производительности и потребления системных ресурсов. Давайте вместе разберемся, как это делать правильно, избегая распространенных ошибок, и какие инструменты нам предлагает платформа.

Мы знаем, что получать реквизиты объекта по ссылке через точку, такие как ДокументСсылка.Организация, является неоптимальным подходом. Почему? Потому что в этом случае платформа вынуждена загружать весь объект целиком из базы данных, включая все его реквизиты и табличные части, даже если нам нужен всего лишь один небольшой реквизит. Это приводит к избыточной нагрузке на сеть, сервер и клиентский компьютер, значительно снижая производительность работы системы — для этого подойдёт поиск причин замедлений и анализ долгих запросов.

Возникают закономерные вопросы: распространяется ли это правило на табличные части? Что происходит, если мы обращаемся к ним напрямую через точку, например, ДокументСсылка.Товары или даже к их свойствам вроде ДокументСсылка.Товары.Количество? Давайте проанализируем эти ситуации.

Опасность прямого обращения к табличным частям по ссылке

Когда мы пытаемся получить табличную часть или ее свойства, используя синтаксис ДокументСсылка.Товары, платформа действует точно так же, как и при обращении к обычным реквизитам. То есть, она полностью загружает весь объект документа из базы данных в память. Это включает в себя все реквизиты шапки документа и, что особенно важно, все строки всех его табличных частей. Даже если нам нужно всего лишь узнать количество строк в табличной части Товары (ДокументСсылка.Товары.Количество) или найти определенные строки (ДокументСсылка.Товары.НайтиСтроки), платформа все равно вынуждена загрузить всю табличную часть, а затем весь объект.

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

Таким образом, прямой доступ к табличным частям по ссылке через точку является крайне неэффективным и должен быть исключен из нашей практики при программировании на 1С.

Различие между ссылкой и объектом в 1С

Чтобы лучше понять суть проблемы, давайте вспомним о ключевом различии между "ссылкой 1С на данные" и "объектом 1С".

  1. Ссылка 1С на данные: Это по сути "адрес" или уникальный идентификатор записи в базе данных. Она занимает минимум ресурсов и доступна только для чтения идентификационных данных (например, уникальный идентификатор, представление). Обращение к ссылкам быстро и экономично, так как не требует загрузки самих данных.
  2. Объект 1С: Это программное представление записи из базы данных, которое содержит все ее данные (реквизиты, табличные части). Объект 1С доступен для чтения, изменения и записи обратно в базу. Создание объекта 1С (неважно, неявно или явно) всегда сопровождается загрузкой его данных из БД, что является ресурсоемкой операцией.

Когда мы обращаемся к реквизиту по ссылке через точку (например, ДокументСсылка.Товары), платформа неявно преобразует ссылку в объект, чтобы получить доступ к его данным. Именно это неявное создание объекта и приводит к полной загрузке всех данных, включая табличные части.

Основной и наиболее эффективный способ: Запросы

Наиболее эффективным и рекомендованным способом получения табличных частей объектов по ссылке в 1С является использование запросов — для этого есть набор инструментов: консоль запросов и кода. Запрос позволяет нам:

  1. Явно указать, какие данные нам нужны: Мы можем выбрать только необходимые колонки из табличной части, что значительно уменьшает объем передаваемых данных.
  2. Избежать загрузки всего объекта: Запрос работает напрямую с базой данных, выбирая только указанные строки и колонки, без необходимости загружать весь документ целиком.
  3. Объединять данные из нескольких таблиц: Мы можем легко получать данные из нескольких связанных таблиц (например, табличная часть документа и реквизиты номенклатуры) за одно обращение к базе данных.

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

Пример 1: Простой запрос для получения табличной части

Допустим, нам нужна вся табличная часть "Товары" документа по его ссылке.


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

    Запрос.УстановитьПараметр("СсылкаДокумента", СсылкаДокумента);

    РезультатЗапроса = Запрос.Выполнить();
    Возврат РезультатЗапроса.Выгрузить(); // Возвращаем ТаблицуЗначений
КонецФункции

// Пример использования
// СсылкаНаРеализацию = Документы.РеализацияТоваровУслуг.НайтиПоНомеру("000000001", Дата("20230101"));
// Если СсылкаНаРеализацию.Пустая() Тогда
//     Сообщить("Документ не найден!");
// Иначе
//     ТабличнаяЧастьТовары = ПолучитьТабличнуюЧастьТовары(СсылкаНаРеализацию);
//     Если ТабличнаяЧастьТовары.Количество() > 0 Тогда
//         Сообщить("Получены строки табличной части: " + ТабличнаяЧастьТовары.Количество());
//         // ТабличнаяЧастьТовары теперь содержит данные
//     КонецЕсли;
// КонецЕсли;

В этом примере мы явно указываем, какие поля нам нужны из табличной части Товары документа РеализацияТоваровУслуг, и фильтруем по ссылке на конкретный документ. В результате мы получаем ТаблицуЗначений только с необходимыми данными, минуя полную загрузку объекта.

Пример 2: Получение табличной части с данными из связанных справочников

Часто нам нужны не просто ссылки на номенклатуру, а ее наименование, артикул и другие свойства.


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

    Запрос.УстановитьПараметр("СсылкаДокумента", СсылкаДокумента);

    РезультатЗапроса = Запрос.Выполнить();
    Возврат РезультатЗапроса.Выгрузить();
КонецФункции

Здесь мы используем точечную нотацию в запросе (ДокументТовары.Номенклатура.Наименование) для получения данных из справочника Номенклатура, связанных с каждой строкой табличной части. Это также выполняется эффективно, поскольку платформа 1С оптимизирует такие запросы.

Альтернативный способ: Функции БСП

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

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

Использование ОбщегоНазначения.ЗначениеРеквизитаОбъекта для табличной части

Хотя эта функция не была специально "предназначена" для получения табличных частей в широком смысле, ее можно использовать для их получения в виде коллекции объектов (например, ТабличнаяЧастьОбъектаКоллекция), которую затем можно выгрузить в ТаблицуЗначений.


Функция ПолучитьТабличнуюЧастьБСП(СсылкаДокумента) Экспорт
    // Получаем коллекцию строк табличной части
    ТабличнаяЧастьКоллекция = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(СсылкаДокумента, "Товары");
    
    // Выгружаем коллекцию в ТаблицуЗначений
    Если ТабличнаяЧастьКоллекция <> Неопределено Тогда
        Возврат ТабличнаяЧастьКоллекция.Выгрузить();
    Иначе
        Возврат Новый ТаблицаЗначений;
    КонецЕсли;
КонецФункции

// Пример использования
// ТабличнаяЧастьТоварыБСП = ПолучитьТабличнуюЧастьБСП(СсылкаНаРеализацию);
// Если ТабличнаяЧастьТоварыБСП.Количество() > 0 Тогда
//     Сообщить("Получены строки табличной части через БСП: " + ТабличнаяЧастьТоварыБСП.Количество());
// КонецЕсли;

Этот подход достаточно лаконичен. Он работает, потому что функция ЗначениеРеквизитаОбъекта, столкнувшись с запросом на получение табличной части, все равно выполняет запрос к базе данных. Однако, вместо того чтобы возвращать сразу ТаблицуЗначений, она возвращает коллекцию, которую нам необходимо выгрузить. Это позволяет избежать полной загрузки объекта документа, но при этом для самой табличной части все равно происходит выборка из БД.

Использование ОбщегоНазначения.ЗначенияРеквизитовОбъекта для нескольких реквизитов, включая табличную часть

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


Функция ПолучитьРеквизитыИТабличнуюЧастьБСП(СсылкаДокумента) Экспорт
    // Получаем несколько реквизитов и табличную часть за одно обращение
    РеквизитыОбъекта = ОбщегоНазначения.ЗначенияРеквизитовОбъекта(СсылкаДокумента, "Организация,Склад,Товары");
    
    Организация = РеквизитыОбъекта.Организация;
    Склад = РеквизитыОбъекта.Склад;
    
    // Табличную часть необходимо выгрузить
    ТабличнаяЧастьТовары = Новый ТаблицаЗначений;
    Если РеквизитыОбъекта.Свойство("Товары") И РеквизитыОбъекта.Товары <> Неопределено Тогда
        ТабличнаяЧастьТовары = РеквизитыОбъекта.Товары.Выгрузить();
    КонецЕсли;
    
    Возврат Новый Структура("Организация,Склад,Товары", Организация, Склад, ТабличнаяЧастьТовары);
КонецФункции

// Пример использования
// Результат = ПолучитьРеквизитыИТабличнуюЧастьБСП(СсылкаНаРеализацию);
// Сообщить("Организация: " + Результат.Организация);
// Сообщить("Склад: " + Результат.Склад);
// Сообщить("Строк в ТЧ Товары: " + Результат.Товары.Количество());

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

Плюсы и минусы использования функций БСП для табличных частей

  1. Плюсы:
    • Лаконичность кода: Вызовы функций БСП часто более кратки, чем написание полноценного запроса.
    • Избегание полной загрузки объекта: Функции БСП не загружают весь объект целиком, а используют внутренний запрос для выборки нужных данных.
    • Удобство для получения нескольких реквизитов: Функция ЗначенияРеквизитовОбъекта позволяет одним вызовом получить и обычные реквизиты, и табличные части, что удобно для комплексных задач.
  2. Минусы:
    • Менее оптимально для одной ТЧ: Если нужна только одна табличная часть без других реквизитов, использование специализированного запроса может быть более производительным. Это связано с тем, что функция БСП хоть и использует запрос, но ее обобщенный характер может добавлять небольшие накладные расходы по сравнению с точно настроенным запросом.
    • Потенциальные накладные расходы: Несмотря на то, что БСП использует запросы, она может выполнять несколько запросов или более сложный универсальный запрос, что в некоторых случаях может быть менее эффективно, чем один, точно настроенный под конкретную задачу.
    • Потребление памяти: Как и любой метод, выгружающий данные в ТаблицуЗначений, он будет потреблять оперативную память. Однако, как отмечают коллеги на форуме, это не сильно отличается от выгрузки результата обычного запроса.

Кэширование объектов и его особенности

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

Сравнение и рекомендации

Давайте подведем итоги и выработаем рекомендации по выбору метода для получения табличных частей:

  1. Запросы – ваш основной инструмент:

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

    Мы настоятельно рекомендуем использовать запросы, когда:

    • Вам нужна только табличная часть или ее часть.
    • Необходимо получить агрегированные данные или данные с отбором.
    • Требуется объединить данные табличной части с данными из других справочников или регистров.
    • Производительность является критически важным фактором.
  2. Функции БСП – удобная альтернатива для специфических случаев:

    Функции ОбщегоНазначения.ЗначениеРеквизитаОбъекта и ОбщегоНазначения.ЗначенияРеквизитовОбъекта могут быть полезны, особенно когда требуется получить не только табличную часть, но и другие реквизиты объекта за один раз. Они обеспечивают лаконичный код и не приводят к полной загрузке всего объекта, так как внутри также используют запросы.

    Рассмотрите использование функций БСП, когда:

    • Вам нужно получить несколько реквизитов, включая одну или несколько табличных частей.
    • Ценится лаконичность кода, и нет жестких требований к абсолютной максимальной производительности (хотя разница может быть минимальной).
    • Вы работаете в управляемом приложении и хотите использовать стандартные механизмы БСП.

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

Заключение

Правильное обращение к табличным частям объектов по ссылке в 1С – это не просто вопрос "как работает", но и вопрос "как работает эффективно". Прямое обращение через точку к табличной части (например, ДокументСсылка.Товары) приводит к избыточной загрузке всего объекта из базы данных, что является крайне неэффективным. Для оптимальной работы мы должны использовать запросы, которые позволяют точно указать необходимые данные и минимизировать нагрузку на систему. В некоторых случаях, для удобства и лаконичности, можно применять функции БСП ОбщегоНазначения.ЗначениеРеквизитаОбъекта или ОбщегоНазначения.ЗначенияРеквизитовОбъекта, но с пониманием того, что они также выполняют внутренние запросы и могут быть менее гибкими или чуть менее производительными, чем специально написанный запрос для конкретной задачи. Выбирая метод, всегда ориентируемся на требования к производительности, объему данных и сложности задачи.

← На главную