Соединения в запросах 1С: левое соединение и разыменование
Соединения в языке запросов работают как в SQL, но у 1С есть своё скрытое соединение: обращение к полю через точку. На собесе дают задачу на левое соединение и смотрят, куда кандидат поставит условие на правую таблицу, и спрашивают, во что превращается точка от составного поля. Результаты сверены на PostgreSQL.
// Так спрашивают: «Чем левое соединение отличается от внутреннего?» «Куда ставить условие на правую таблицу?» «Что происходит при обращении через точку?» «Зачем ВЫРАЗИТЬ у составного поля?»
Внутреннее и левое
Внутреннее соединение оставляет только строки, у которых нашлась пара. Левое соединение сохраняет все строки левой таблицы, а у строк без пары поля правой будут NULL. Полное сохраняет строки обеих сторон. Три контрагента и их продажи: у Василька продаж нет.
С внутренним соединением Василёк пропал бы из отчёта, а задача «все контрагенты и их продажи» требует видеть и тех, кто ничего не купил.
ВЫБРАТЬ
К.Наименование,
ЕСТЬNULL(СУММА(Р.СуммаДокумента), 0) КАК Продажи
ИЗ
Справочник.Контрагенты КАК К
ЛЕВОЕ СОЕДИНЕНИЕ Документ.Реализация КАК Р
ПО К.Ссылка = Р.Контрагент
СГРУППИРОВАТЬ ПО
К.Наименование
УПОРЯДОЧИТЬ ПО
К.НаименованиеУсловие в ПО или в ГДЕ
Нужны все контрагенты и только их крупные продажи, больше 500. Условие на правую таблицу можно поставить в ПО или в ГДЕ, и результаты будут разными:
В ГДЕ условие проверяется после соединения. У Василька Р.СуммаДокумента равна NULL, сравнение с NULL не истинно, и строка ушла: левое соединение превратилось во внутреннее. В ПО условие участвует в подборе пары, и Василёк остаётся с NULL. Правило: условия на правую таблицу левого соединения пишут в ПО.
// условие в ГДЕ
ВЫБРАТЬ К.Наименование, Р.Номер
ИЗ Справочник.Контрагенты КАК К
ЛЕВОЕ СОЕДИНЕНИЕ Документ.Реализация КАК Р
ПО К.Ссылка = Р.Контрагент
ГДЕ Р.СуммаДокумента > 500
УПОРЯДОЧИТЬ ПО К.НаименованиеТочка как соединение
Разыменование Р.Контрагент.ИНН выглядит как чтение поля, а на деле платформа добавляет левое соединение с таблицей контрагентов. Одна точка дешёвая, но точка от составного поля дорогая: если Регистратор может быть десятью видами документов, Регистратор.Дата даст соединение с каждой из десяти таблиц.
Если нужен один тип, поле приводят ВЫРАЗИТЬ: ВЫРАЗИТЬ(Движения.Регистратор КАК Документ.Реализация).Номер, и соединение будет одно. Проверить тип без соединения позволяет Движения.Регистратор ССЫЛКА Документ.Реализация.
Размножение строк
Если справа для одной строки нашлись две пары, в результате будут две строки. Соединили продажи с ценами без среза, у товара две цены, и сумма продаж удвоилась. Правую сторону сначала сворачивают до одной строки на ключ: срез на дату, группировка, временная таблица.
Соединение с вложенным запросом оптимизатор СУБД (система управления базами данных) оценивает плохо и часто выбирает неудачный план. Вложенный запрос выносят во временную таблицу. Условие соединения через ИЛИ ломает индексы, такой запрос делят на две части и склеивают ОБЪЕДИНИТЬ ВСЕ.
Как отвечать: «Нужен список всех контрагентов с суммой их продаж больше 500. Где поставишь условие на сумму?»
В условии соединения, в ПО. Если поставить его в ГДЕ, оно проверится после соединения, у контрагентов без продаж сумма будет NULL, сравнение не пройдёт, и они пропадут, то есть левое соединение станет внутренним. В ПО условие участвует в подборе пары, и контрагент без крупных продаж останется в списке с пустыми полями. Пустоту потом закрываю ЕСТЬNULL.
Кандидат объясняет механизм через NULL, а не правилом «так принято». Это классический вопрос, и уверенный ответ сразу отделяет практика.
На чём валят
- −Пишут условие на правую таблицу левого соединения в ГДЕ. Строки с NULL уходят, соединение становится внутренним. Условие ставят в ПО.
- −Обращаются через точку к составному полю. Платформа соединяет со всеми таблицами типа. Поле приводят ВЫРАЗИТЬ.
- −Соединяют с правой таблицей, где на ключ несколько строк. Строки размножаются, суммы удваиваются. Правую сторону сворачивают до соединения.
- −Соединяют с вложенным запросом. Оптимизатор плохо оценивает его размер. Выносят во временную таблицу.
Проверь себя
Пять вопросов из банка по этой подтеме. Всего их 12, остальные разбираются в тренажёре.
- Что делает обращение через точку в запросе:
Реал.Контрагент.ИНН?A)Выполняет подзапрос на каждую строкуB)Читает ИНН из кэша сеанса на сервереC)Неявно соединяет документ со справочникомD)Ошибку: в запросе точку использовать нельзяпоказать ответ и разбор
+C)Неявно соединяет документ со справочником// разбор: Разыменование поля ссылочного типа в запросе превращается в левое соединение с таблицей объекта. Для одного уровня это нормально. Длинные цепочки точек и составные типы порождают много соединений, и запрос становится тяжёлым — тогда соединения пишут явно или берут нужные поля во временную таблицу.
- Какое соединение вернёт товары и из прайса, и со склада, даже если товар есть только в одном из источников?A)ЛЕВОЕB)ПОЛНОЕC)ВНУТРЕННЕЕD)ПРАВОЕ
показать ответ и разбор
+B)ПОЛНОЕ// разбор: Полное соединение сохраняет строки обеих таблиц без пары, заполняя отсутствующую сторону NULL. Чтобы получить одно поле товара, берут ЕСТЬNULL(Прайс.Товар, Склад.Товар). Альтернатива без соединения — ОБЪЕДИНИТЬ ВСЕ и группировка.
- Что вернёт запрос для товара без цены?
ВЫБРАТЬ Т.Ссылка, ЕСТЬNULL(Ц.Цена, 0) КАК Цена ИЗ Справочник.Товары КАК Т ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.Цены КАК Ц ПО Т.Ссылка = Ц.ТоварA)Строку товара с ценой 0B)Строку товара с ценой NULLC)Ничего: товар без цены отброситсяD)Ошибку: ЕСТЬNULL нельзя в выборкепоказать ответ и разбор
+A)Строку товара с ценой 0// разбор: Левое соединение сохраняет товар без пары, поле Ц.Цена у него NULL, а ЕСТЬNULL заменяет его нулём. Если у товара несколько цен на разные даты, строк станет несколько — для одной цены нужен срез последних. ЕСТЬNULL допустим и в выборке, и в условиях.
- Где правильнее поставить условие на правую таблицу при ЛЕВОМ соединении, чтобы не потерять строки левой?A)В условии ПОB)В ГДЕ после соединенияC)В ИМЕЮЩИЕ после группировкиD)Без разницы, СУБД перенесёт сама
показать ответ и разбор
+A)В условии ПО// разбор: Условие в ГДЕ на поле правой таблицы отбросит строки, где оно NULL, и левое соединение фактически станет внутренним. В секции ПО условие только ограничивает, какие правые строки подходят в пару, а левые строки сохраняются. Классическая ошибка в отчётах «пропали товары без продаж».
- Поле
Регистраторв регистре — составного типа из 40 видов документов. Чем опасноР.Регистратор.Датав запросе?A)Регистратор нельзя разыменовывать в запросеB)Дата недоступна у регистратора составного типаC)Запрос вернёт NULL для всех строкD)Соединение со всеми 40 таблицамипоказать ответ и разбор
+D)Соединение со всеми 40 таблицами// разбор: Платформа не знает, какой документ в каждой строке, и строит соединение с таблицей каждого типа из состава. Сорок соединений превращают простой запрос в тяжёлый. Если нужен один тип, пишут ВЫРАЗИТЬ(Р.Регистратор КАК Документ.Реализация).Дата; дата регистратора есть и в поле Период.
дальше
Теорию прочитал. Навык ставится повторением
В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, бесплатная часть практики — одна сессия дня в сутки.