При работе с запросами в 1С часто возникает необходимость использовать временные таблицы для промежуточного хранения данных. Это помогает разбить сложный запрос на более простые этапы, улучшить читаемость кода и оптимизировать производительность. Однако, многие разработчики сталкиваются с проблемой при попытке обращения к вложенным табличным частям документов, ссылки на которые хранятся во временной таблице. Рассмотрим эту ситуацию подробнее и выясним, как правильно работать с такими данными.
Исходная проблема, с которой столкнулся автор, заключалась в попытке получить данные табличной части Товары документа СчетНаОплатуПокупателю, когда во временной таблице ТабСчета хранились только ссылки на эти документы. Запрос выглядел примерно так:
ВЫБРАТЬ
СчетНаОплатуПокупателюТовары.Номенклатура,
СчетНаОплатуПокупателюТовары.Количество
ИЗ
ТабСчета.Ссылка.Товары КАК СчетНаОплатуПокупателюТовары
Такой подход приводит к ошибке, потому что временная таблица (ВТ) ТабСчета «знает» только о тех полях, которые мы в нее явно поместили. В данном случае, мы поместили в нее только поле Ссылка (СчетНаОплатуПокупателю.Ссылка). Временная таблица не сохраняет информацию о том, что эта ссылка относится к документу СчетНаОплатуПокупателю, и уж тем более она не содержит сведений о его структуре, включая табличные части. Для системы это просто набор значений типа «Ссылка». Таким образом, попытка обращения через точку к ТабСчета.Ссылка.Товары является неверной, поскольку у поля Ссылка внутри временной таблицы нет свойства Товары.
Когда мы помещаем СчетНаОплатуПокупателю.Ссылка во временную таблицу, в ней хранится только ссылка на документ, а не сам объект документа со всеми его табличными частями. Временные таблицы содержат только те поля, которые явно указаны в предложении ВЫБРАТЬ запроса ПОМЕСТИТЬ. Они не "знают" о структуре объекта, от которого была получена ссылка, и не могут автоматически раскрыть его табличные части.
Наиболее распространенный и правильный подход для работы с табличными частями документов, ссылки на которые хранятся во временной таблице, — это соединение временной таблицы с реальной табличной частью нужного документа во втором пакете запроса. Рассмотрим этот процесс по шагам.
Сначала мы формируем временную таблицу ТабСчета, содержащую ссылки на те документы СчетНаОплатуПокупателю, которые удовлетворяют определенным условиям. Оригинальный запрос автора уже выполняет эту задачу:
ВЫБРАТЬ
СчетНаОплатуПокупателю.Ссылка КАК Ссылка
ПОМЕСТИТЬ ТабСчета
ИЗ
Документ.СчетНаОплатуПокупателю КАК СчетНаОплатуПокупателю
ЛЕВОЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг КАК РеализацияТоваровУслуг
ПО СчетНаОплатуПокупателю.Ссылка = РеализацияТоваровУслуг.ДокументОснование
ГДЕ
СчетНаОплатуПокупателю.Дата >= &Дата
И РеализацияТоваровУслуг.Проведен
СГРУППИРОВАТЬ ПО
СчетНаОплатуПокупателю.Ссылка
ИМЕЮЩИЕ
НЕ СУММА(РеализацияТоваровУслуг.СуммаДокумента) = СчетНаОплатуПокупателю.СуммаДокумента;
В этом запросе мы отбираем счета на оплату покупателю, которые имеют проведенные реализации, но сумма реализованных товаров по этим реализациям не равна общей сумме счета. Результат запроса — список ссылок на документы СчетНаОплатуПокупателю, которые будут помещены в нашу временную таблицу ТабСчета. Важно понимать, что в ТабСчета будет только одно поле — Ссылка.
После того как временная таблица ТабСчета сформирована, мы можем использовать ее во втором пакете запроса. Для того чтобы получить данные из табличной части Товары, мы должны выполнить соединение нашей временной таблицы ТабСчета с табличной частью самого документа Документ.СчетНаОплатуПокупателю.Товары. Это позволит связать ссылки из временной таблицы с соответствующими записями в табличной части.
Рассмотрим пример корректного соединения:
ВЫБРАТЬ
СчетНаОплатуПокупателюТовары.Номенклатура,
СчетНаОплатуПокупателюТовары.Количество
ИЗ
ТабСчета КАК ТабСчета
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.СчетНаОплатуПокупателю.Товары КАК СчетНаОплатуПокупателюТовары
ПО ТабСчета.Ссылка = СчетНаОплатуПокупателюТовары.Ссылка
В этом запросе мы делаем следующее:
ТабСчета как к источнику данных.Документ.СчетНаОплатуПокупателю.Товары. Эта таблица содержит все строки табличной части "Товары" для всех документов "Счет на оплату покупателю".ПО ТабСчета.Ссылка = СчетНаОплатуПокупателюТовары.Ссылка мы связываем строки временной таблицы с записями в табличной части, где Ссылка (родительский документ табличной части) совпадает со ссылкой из нашей временной таблицы.Номенклатуры и Количества только для тех товаров, которые относятся к документам, отобранным в первом пакете и помещенным во временную таблицу ТабСчета.В приведенном выше примере мы использовали ВНУТРЕННЕЕ СОЕДИНЕНИЕ (INNER JOIN). Давайте разберем, когда уместно использовать его, а когда ЛЕВОЕ СОЕДИНЕНИЕ (LEFT JOIN):
ВНУТРЕННЕЕ СОЕДИНЕНИЕ (INNER JOIN):
Вернет только те строки из табличной части Документ.СчетНаОплатуПокупателю.Товары, для которых нашлись соответствующие ссылки в ТабСчета. При этом, если документ из ТабСчета не имеет записей в табличной части Товары (что маловероятно для СчетНаОплатуПокупателю, так как документ без товаров не имеет смысла), он не попадет в конечный результат запроса. Если наша цель — получить только те товары, которые реально присутствуют в отобранных счетах, то ВНУТРЕННЕЕ СОЕДИНЕНИЕ является наиболее подходящим.
ЛЕВОЕ СОЕДИНЕНИЕ (LEFT JOIN):
Вернет все документы из ТабСчета. Для тех документов, у которых есть записи в табличной части Товары, будут добавлены соответствующие строки с номенклатурой и количеством. Если же у какого-либо документа из ТабСчета нет записей в табличной части Товары, то поля из табличной части (Номенклатура, Количество) для этого документа будут иметь значение NULL. ЛЕВОЕ СОЕДИНЕНИЕ может быть полезно, если нам нужно получить *все* отобранные счета (даже те, у которых по каким-то причинам нет товаров, хотя это исключительная ситуация для счета) и при этом увидеть их товары, если они есть. Однако в данном конкретном случае, когда целью является получение именно товаров, а не счетов без товаров, ВНУТРЕННЕЕ СОЕДИНЕНИЕ часто более точно отражает требуемый результат.
Если после ЛЕВОГО СОЕДИНЕНИЯ вы добавляете условие ГДЕ НЕ ТабСчета.Ссылка ЕСТЬ NULL (как было предложено в одном из сообщений), это фактически превращает ЛЕВОЕ СОЕДИНЕНИЕ во ВНУТРЕННЕЕ СОЕДИНЕНИЕ, поскольку отсекаются все строки, для которых соответствие не было найдено в правой таблице. Поэтому, если вам нужны только совпадения, лучше сразу использовать ВНУТРЕННЕЕ СОЕДИНЕНИЕ.
Хотя соединение является предпочтительным способом, существует также возможность использования подзапроса в условии ГДЕ ... В (...). Это менее производительный, но иногда более наглядный способ для очень простых случаев:
ВЫБРАТЬ
СчетНаОплатуПокупателюТовары.Номенклатура,
СчетНаОплатуПокупателюТовары.Количество
ИЗ
Документ.СчетНаОплатуПокупателю.Товары КАК СчетНаОплатуПокупателюТовары
ГДЕ
СчетНаОплатуПокупателюТовары.Ссылка В (ВЫБРАТЬ ТабСчета.Ссылка ИЗ ТабСчета КАК ТабСчета)
Этот вариант также будет работать, но его производительность может быть ниже, особенно на больших объемах данных, по сравнению с явным соединением таблиц. Оптимизатору СУБД сложнее определить количество записей во вложенном запросе, что может привести к неоптимальному плану выполнения.
Использование временных таблиц: Временные таблицы (ВТ) — мощный инструмент для оптимизации сложных запросов. Они позволяют разделить логику на этапы: сначала отбор необходимых сущностей (в нашем случае — документов), затем работа с их детальными данными (табличными частями). Это особенно эффективно при больших объемах данных, когда формирование ВТ с уже отфильтрованным набором ссылок значительно уменьшает объем данных для последующих операций.
Производительность соединений: Соединение временной таблицы с табличной частью документа является стандартным и наиболее производительным способом. Система управления базами данных (СУБД) хорошо оптимизирована для выполнения операций соединения. Отделение этапа фильтрации документов в отдельный пакет с помещением результатов во временную таблицу позволяет СУБД эффективно индексировать и обрабатывать данные на каждом шаге.
Осторожность с вложенными запросами: Стандарты 1С не рекомендуют делать соединения со вложенными запросами, вместо этого рекомендуется использовать соединения с временными таблицами. Это связано с тем, что оптимизатору СУБД сложно определить количество записей во вложенном запросе, что может привести к неоптимальному плану выполнения. Вложенный запрос ВЫБРАТЬ ТабСчета.Ссылка ИЗ ТабСчета КАК ТабСчета в условии ГДЕ ... В (...) является примером такого подхода, который может быть менее эффективным, чем явное соединение.
Итак, чтобы корректно обратиться к табличной части документа, ссылки на который хранятся во временной таблице, всегда используйте механизм соединения. Создайте временную таблицу с необходимыми ссылками, а затем во втором пакете запроса выполните ВНУТРЕННЕЕ СОЕДИНЕНИЕ (или ЛЕВОЕ СОЕДИНЕНИЕ, в зависимости от вашей бизнес-логики) этой временной таблицы с полной таблицей табличной части документа. Этот подход является наиболее гибким, производительным и соответствует лучшим практикам разработки в 1С.
Теперь вы знаете, что временная таблица хранит только те поля, которые в нее явно помещены, и не "знает" о структуре объектов, от которых были получены ссылки. Это понимание является ключевым для правильного построения сложных запросов в 1С.