Приветствуем, коллеги! Сегодня мы рассмотрим очень важную и часто встречающуюся проблему при создании запросов в 1С: как корректно выполнить отбор данных по реквизитам, которые находятся не в основной таблице объекта (например, справочника или документа), а в его табличной части. Эта задача может вызвать затруднения, особенно у начинающих разработчиков, и мы разберем ее по шагам.
Исходная проблема, с которой мы столкнулись, заключается в попытке обратиться к реквизиту табличной части напрямую из основной таблицы объекта в условии отбора ГДЕ. Давайте посмотрим на некорректный пример запроса, который привел к ошибке:
ВЫБРАТЬ
пто_СтавкиФакт.Ссылка КАК Ссылка,
пто_СтавкиФакт.Ссылка.КарточкаРаботСсылка КАК КарточкаРаботСсылка,
пто_СтавкиФакт.Ссылка.КарточкаРаботСсылка.ДатаРабот КАК ДатаРабот,
пто_СтавкиФакт.КарточкаРаботСсылка.Объект.ДокументыСведенияБух.(
Дата КАК КарточкаРаботСсылкаОбъектДата
) КАК КарточкаРаботСсылкаОбъект
ИЗ
Справочник.пто_СтавкиФакт КАК пто_СтавкиФакт
ГДЕ
пто_СтавкиФакт.ВОтчет2 = &ВОтчет2
И пто_СтавкиФакт.ПометкаУдаления = &ПометкаУдаления
И пто_СтавкиФакт.КарточкаРаботСсылка.Объект.ДокументыСведенияБух.Дата МЕЖДУ &ДатаРаботНачало И &ДатаРаботОкончание
В этом запросе мы видим попытку обратиться к пто_СтавкиФакт.КарточкаРаботСсылка.Объект.ДокументыСведенияБух.Дата в секции ГДЕ. Это принципиально неверно, потому что основная таблица объекта не содержит в себе полей своих табличных частей. Табличная часть — это, по сути, отдельная, подчиненная таблица, которая лишь логически связана с основной по полю Ссылка. Мы не можем "проваливаться" в табличную часть через точечную нотацию в условиях отбора к основной таблице.
Чтобы лучше понять это, представим структуру данных. Каждый объект 1С (справочник, документ и т.д.) имеет основную таблицу, которая хранит его "шапку" или общие реквизиты. Если у объекта есть табличные части, то для каждой такой табличной части создается отдельная физическая таблица в базе данных. Эти отдельные таблицы связаны с основной таблицей объекта через поле Ссылка. Таким образом, когда мы пишем запрос только к основной таблице (например, Справочник.пто_СтавкиФакт), мы получаем доступ только к ее собственным полям, но не к полям связанных с ней табличных частей.
Чтобы получить доступ к полям табличной части для отбора или вывода в результат, нам необходимо явно указать 1С, что мы хотим работать с обеими таблицами — основной и табличной частью — и как они связаны. Для этого мы используем соединения таблиц (JOIN).
Как нам верно подсказали на форуме, ключевым решением здесь является использование операторов соединения таблиц. Давайте разберем этот процесс пошагово.
Определяем источники данных.
Прежде всего, нам нужно четко понимать, из каких таблиц мы будем выбирать данные. В нашем случае это:
Справочник.пто_СтавкиФакт. Она содержит реквизиты "шапки" справочника.Справочник.пто_СтавкиФакт.ДокументыСведенияБух (имя табличной части обычно указывается через точку после имени объекта). Именно здесь находится искомая дата ("Дата ввода в эксплуатацию" в нашем примере, или просто "Дата" в исходном запросе).Мы будем строить запрос таким образом, чтобы он "видел" поля из обеих этих таблиц одновременно.
Выбираем тип соединения.
В 1С, как и в SQL, существует несколько типов соединений. Для нашей задачи чаще всего используются ВНУТРЕННЕЕ СОЕДИНЕНИЕ (ВНУТРЕННЕЕ СОЕДИНЕНИЕ / INNER JOIN) или ЛЕВОЕ СОЕДИНЕНИЕ (ЛЕВОЕ СОЕДИНЕНИЕ / LEFT JOIN). Выбор типа соединения зависит от того, какой результат мы хотим получить:
ВНУТРЕННЕЕ СОЕДИНЕНИЕ (ВНУТРЕННЕЕ СОЕДИНЕНИЕ):
Этот тип соединения возвращает только те записи, для которых есть совпадения в обеих соединяемых таблицах. Если мы используем ВНУТРЕННЕЕ СОЕДИНЕНИЕ между основной таблицей и табличной частью, то в результат запроса попадут строки только тех элементов справочника, у которых есть хотя бы одна запись в указанной табличной части, соответствующая условию связи. Если нам нужно получить только те элементы, у которых в табличной части есть нужная дата, это наш выбор.
ЛЕВОЕ СОЕДИНЕНИЕ (ЛЕВОЕ СОЕДИНЕНИЕ):
Этот тип соединения возвращает все записи из левой таблицы (в нашем случае, из основной таблицы объекта), а также совпадающие записи из правой таблицы (табличной части). Если для какой-то записи из левой таблицы в правой таблице не найдется соответствующих строк, поля из правой таблицы будут иметь значение NULL. Этот тип соединения полезен, когда мы хотим получить все основные объекты, а данные из табличной части — только если они существуют, и не хотим терять основные объекты, у которых табличная часть может быть пустой.
В нашем случае, поскольку мы хотим отобрать записи по дате в табличной части, нам нужны только те записи, где такая дата есть, поэтому ВНУТРЕННЕЕ СОЕДИНЕНИЕ будет наиболее подходящим выбором.
Устанавливаем условие связи.
Связь между основной таблицей и ее табличной частью всегда осуществляется по полю Ссылка. Это поле присутствует как в основной таблице (как первичный ключ), так и в табличной части (как ссылка на владельца). Условие связи будет выглядеть так:
ПО
ОсновнаяТаблица.Ссылка = ТаблицаТабличнойЧасти.Ссылка
Здесь ОсновнаяТаблица и ТаблицаТабличнойЧасти - это псевдонимы, которые мы присвоим нашим источникам данных в запросе.
Налагаем условие отбора.
После того как таблицы соединены, мы можем без проблем использовать поля из табличной части в секции ГДЕ. Теперь поле Дата из табличной части становится доступным, как если бы оно было частью одной большой объединенной таблицы.
Давайте адаптируем пример, который мы обсуждали. Представим, что у нас есть справочник Справочник.ПТО_СтавкиФакт, и в его табличной части ДокументыСведенияБух есть поле Дата, по которому нам нужно сделать отбор (это наша "Дата ввода в эксплуатацию"). Мы хотим выбрать все элементы справочника, у которых в табличной части есть записи с датой в определенном периоде.
ВЫБРАТЬ
ОсновнойСправочник.Ссылка,
ОсновнойСправочник.Ссылка.КарточкаРаботСсылка КАК КарточкаРаботСсылка,
ОсновнойСправочник.Ссылка.КарточкаРаботСсылка.ДатаРабот КАК ДатаРабот,
ТабличнаяЧасть.Дата КАК ДатаВводаВЭксплуатацию
ИЗ
Справочник.ПТО_СтавкиФакт КАК ОсновнойСправочник
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.ПТО_СтавкиФакт.ДокументыСведенияБух КАК ТабличнаяЧасть
ПО ОсновнойСправочник.Ссылка = ТабличнаяЧасть.Ссылка
ГДЕ
ОсновнойСправочник.ВОтчет2 = &ВОтчет2
И ОсновнойСправочник.ПометкаУдаления = &ПометкаУдаления
И ТабличнаяЧасть.Дата МЕЖДУ &ДатаНачалаПериода И &ДатаОкончанияПериода
ИЗ Справочник.ПТО_СтавкиФакт КАК ОсновнойСправочник
Мы указываем нашу основную таблицу — справочник ПТО_СтавкиФакт — и даем ей псевдоним ОсновнойСправочник. Это облегчает чтение запроса.
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.ПТО_СтавкиФакт.ДокументыСведенияБух КАК ТабличнаяЧасть
Здесь мы присоединяем к ОсновнойСправочник таблицу табличной части ДокументыСведенияБух из того же справочника. Мы также даем ей псевдоним ТабличнаяЧасть. Мы используем ВНУТРЕННЕЕ СОЕДИНЕНИЕ, так как нам нужны только те записи из основной таблицы, для которых существуют соответствующие записи в табличной части с указанной датой.
ПО ОсновнойСправочник.Ссылка = ТабличнаяЧасть.Ссылка
Это ключевое условие связи. Мы соединяем записи из основной таблицы с записями из табличной части по их общему полю Ссылка. Это гарантирует, что каждая строка из табличной части будет связана со своим "владельцем" — элементом справочника.
ВЫБРАТЬ ... ТабличнаяЧасть.Дата КАК ДатаВводаВЭксплуатацию
После соединения таблиц, поля из ТабличнаяЧасть становятся доступными для выбора и могут быть использованы в списке полей для вывода в результат. Мы выбираем Дата из табличной части и даем ей более осмысленное имя ДатаВводаВЭксплуатацию.
ГДЕ ... И ТабличнаяЧасть.Дата МЕЖДУ &ДатаНачалаПериода И &ДатаОкончанияПериода
Теперь, когда ТабличнаяЧасть доступна как источник данных, мы можем без проблем использовать ее поля в условии отбора. Мы фильтруем записи по полю Дата из табличной части, проверяя, попадает ли оно в заданный интервал дат.
Важно отметить, что такой подход является стандартной и наиболее эффективной практикой в 1С для работы с данными табличных частей. Он обеспечивает корректную выборку и отбор данных, избегая ошибок, связанных с некорректным обращением к структуре объектов.
Вложенные запросы (когда один запрос выполняется внутри другого) также являются мощным инструментом. Однако в данном случае, когда нам нужно просто получить данные из табличной части и связать их с основной, прямое соединение является более читаемым, простым и часто более производительным решением. Вложенные запросы целесообразно использовать, когда вам нужно сначала агрегировать данные в табличной части (например, посчитать сумму по полю, найти минимальное/максимальное значение) или применить сложные условия к группе строк табличной части, а затем результат этого агрегирования связать с основной таблицей.
Например, если бы нам нужно было выбрать все справочники, у которых средняя дата ввода в эксплуатацию за период попадает в некий диапазон, тогда вложенный запрос мог бы быть оправдан. Но для простого отбора по значению поля, как в нашем случае, соединение — это то, что надо.
Изучите основы SQL/языка запросов 1С: Понимание принципов реляционных баз данных и операторов SELECT, FROM, JOIN, WHERE, GROUP BY и ORDER BY является фундаментальным.
Используйте конструктор запросов: В 1С есть отличный конструктор запросов, который помогает визуально строить запросы, выбирать таблицы, устанавливать связи и условия. Он генерирует текст запроса, который можно потом проанализировать и понять логику. Для новичков это незаменимый инструмент для обучения.
Экспериментируйте с псевдонимами: Присваивайте источникам данных короткие, но понятные псевдонимы (как ОсновнойСправочник и ТабличнаяЧасть в нашем примере). Это значительно улучшает читаемость запроса.
Проверяйте план запроса: Если запрос выполняется медленно, используйте инструменты отладки 1С (например, Консоль запросов), чтобы посмотреть план выполнения запроса. Это поможет выявить узкие места.
Помните, что построение запросов в 1С — это ключевой навык разработчика. Освоив принципы работы с соединениями таблиц, вы сможете эффективно извлекать и обрабатывать данные любой сложности. Удачи в ваших начинаниях!