← вся теориятеория к собесу · Язык запросов 1С

Соединения в запросах 1С: левое соединение и разыменование

разбор писал Даниил, автор Сеньорчика

Зачем это спрашивают

Соединения в языке запросов работают как в SQL, но у 1С есть своё скрытое соединение: обращение к полю через точку. На собесе дают задачу на левое соединение и смотрят, куда кандидат поставит условие на правую таблицу, и спрашивают, во что превращается точка от составного поля. Результаты сверены на PostgreSQL.

// Так спрашивают: «Чем левое соединение отличается от внутреннего?» «Куда ставить условие на правую таблицу?» «Что происходит при обращении через точку?» «Зачем ВЫРАЗИТЬ у составного поля?»

Внутреннее и левое

Внутреннее соединение оставляет только строки, у которых нашлась пара. Левое соединение сохраняет все строки левой таблицы, а у строк без пары поля правой будут NULL. Полное сохраняет строки обеих сторон. Три контрагента и их продажи: у Василька продаж нет.

С внутренним соединением Василёк пропал бы из отчёта, а задача «все контрагенты и их продажи» требует видеть и тех, кто ничего не купил.

ВЫБРАТЬ
    К.Наименование,
    ЕСТЬNULL(СУММА(Р.СуммаДокумента), 0) КАК Продажи
ИЗ
    Справочник.Контрагенты КАК К
        ЛЕВОЕ СОЕДИНЕНИЕ Документ.Реализация КАК Р
        ПО К.Ссылка = Р.Контрагент
СГРУППИРОВАТЬ ПО
    К.Наименование
УПОРЯДОЧИТЬ ПО
    К.Наименование

Условие в ПО или в ГДЕ

Нужны все контрагенты и только их крупные продажи, больше 500. Условие на правую таблицу можно поставить в ПО или в ГДЕ, и результаты будут разными:

В ГДЕ условие проверяется после соединения. У Василька Р.СуммаДокумента равна NULL, сравнение с NULL не истинно, и строка ушла: левое соединение превратилось во внутреннее. В ПО условие участвует в подборе пары, и Василёк остаётся с NULL. Правило: условия на правую таблицу левого соединения пишут в ПО.

// условие в ГДЕ
ВЫБРАТЬ К.Наименование, Р.Номер
ИЗ Справочник.Контрагенты КАК К
    ЛЕВОЕ СОЕДИНЕНИЕ Документ.Реализация КАК Р
    ПО К.Ссылка = Р.Контрагент
ГДЕ Р.СуммаДокумента > 500
УПОРЯДОЧИТЬ ПО К.Наименование

Точка как соединение

Разыменование Р.Контрагент.ИНН выглядит как чтение поля, а на деле платформа добавляет левое соединение с таблицей контрагентов. Одна точка дешёвая, но точка от составного поля дорогая: если Регистратор может быть десятью видами документов, Регистратор.Дата даст соединение с каждой из десяти таблиц.

Если нужен один тип, поле приводят ВЫРАЗИТЬ: ВЫРАЗИТЬ(Движения.Регистратор КАК Документ.Реализация).Номер, и соединение будет одно. Проверить тип без соединения позволяет Движения.Регистратор ССЫЛКА Документ.Реализация.

Размножение строк

Если справа для одной строки нашлись две пары, в результате будут две строки. Соединили продажи с ценами без среза, у товара две цены, и сумма продаж удвоилась. Правую сторону сначала сворачивают до одной строки на ключ: срез на дату, группировка, временная таблица.

Соединение с вложенным запросом оптимизатор СУБД (система управления базами данных) оценивает плохо и часто выбирает неудачный план. Вложенный запрос выносят во временную таблицу. Условие соединения через ИЛИ ломает индексы, такой запрос делят на две части и склеивают ОБЪЕДИНИТЬ ВСЕ.

Как отвечать: «Нужен список всех контрагентов с суммой их продаж больше 500. Где поставишь условие на сумму?»

В условии соединения, в ПО. Если поставить его в ГДЕ, оно проверится после соединения, у контрагентов без продаж сумма будет NULL, сравнение не пройдёт, и они пропадут, то есть левое соединение станет внутренним. В ПО условие участвует в подборе пары, и контрагент без крупных продаж останется в списке с пустыми полями. Пустоту потом закрываю ЕСТЬNULL.

Кандидат объясняет механизм через NULL, а не правилом «так принято». Это классический вопрос, и уверенный ответ сразу отделяет практика.

На чём валят

  • −Пишут условие на правую таблицу левого соединения в ГДЕ. Строки с NULL уходят, соединение становится внутренним. Условие ставят в ПО.
  • −Обращаются через точку к составному полю. Платформа соединяет со всеми таблицами типа. Поле приводят ВЫРАЗИТЬ.
  • −Соединяют с правой таблицей, где на ключ несколько строк. Строки размножаются, суммы удваиваются. Правую сторону сворачивают до соединения.
  • −Соединяют с вложенным запросом. Оптимизатор плохо оценивает его размер. Выносят во временную таблицу.

Проверь себя

Пять вопросов из банка по этой подтеме. Всего их 12, остальные разбираются в тренажёре.

  1. #oc_q_joins1 / 5
    Что делает обращение через точку в запросе: Реал.Контрагент.ИНН?
    A)Выполняет подзапрос на каждую строку
    B)Читает ИНН из кэша сеанса на сервере
    C)Неявно соединяет документ со справочником
    D)Ошибку: в запросе точку использовать нельзя
    показать ответ и разбор
    +C)Неявно соединяет документ со справочником

    // разбор: Разыменование поля ссылочного типа в запросе превращается в левое соединение с таблицей объекта. Для одного уровня это нормально. Длинные цепочки точек и составные типы порождают много соединений, и запрос становится тяжёлым — тогда соединения пишут явно или берут нужные поля во временную таблицу.

  2. #oc_q_joins2 / 5
    Какое соединение вернёт товары и из прайса, и со склада, даже если товар есть только в одном из источников?
    A)ЛЕВОЕ
    B)ПОЛНОЕ
    C)ВНУТРЕННЕЕ
    D)ПРАВОЕ
    показать ответ и разбор
    +B)ПОЛНОЕ

    // разбор: Полное соединение сохраняет строки обеих таблиц без пары, заполняя отсутствующую сторону NULL. Чтобы получить одно поле товара, берут ЕСТЬNULL(Прайс.Товар, Склад.Товар). Альтернатива без соединения — ОБЪЕДИНИТЬ ВСЕ и группировка.

  3. #oc_q_joins3 / 5
    Что вернёт запрос для товара без цены?
    ВЫБРАТЬ
      Т.Ссылка,
      ЕСТЬNULL(Ц.Цена, 0) КАК Цена
    ИЗ
      Справочник.Товары КАК Т
      ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.Цены
        КАК Ц
      ПО Т.Ссылка = Ц.Товар
    A)Строку товара с ценой 0
    B)Строку товара с ценой NULL
    C)Ничего: товар без цены отбросится
    D)Ошибку: ЕСТЬNULL нельзя в выборке
    показать ответ и разбор
    +A)Строку товара с ценой 0

    // разбор: Левое соединение сохраняет товар без пары, поле Ц.Цена у него NULL, а ЕСТЬNULL заменяет его нулём. Если у товара несколько цен на разные даты, строк станет несколько — для одной цены нужен срез последних. ЕСТЬNULL допустим и в выборке, и в условиях.

  4. #oc_q_joins4 / 5
    Где правильнее поставить условие на правую таблицу при ЛЕВОМ соединении, чтобы не потерять строки левой?
    A)В условии ПО
    B)В ГДЕ после соединения
    C)В ИМЕЮЩИЕ после группировки
    D)Без разницы, СУБД перенесёт сама
    показать ответ и разбор
    +A)В условии ПО

    // разбор: Условие в ГДЕ на поле правой таблицы отбросит строки, где оно NULL, и левое соединение фактически станет внутренним. В секции ПО условие только ограничивает, какие правые строки подходят в пару, а левые строки сохраняются. Классическая ошибка в отчётах «пропали товары без продаж».

  5. #oc_q_joins5 / 5
    Поле Регистратор в регистре — составного типа из 40 видов документов. Чем опасно Р.Регистратор.Дата в запросе?
    A)Регистратор нельзя разыменовывать в запросе
    B)Дата недоступна у регистратора составного типа
    C)Запрос вернёт NULL для всех строк
    D)Соединение со всеми 40 таблицами
    показать ответ и разбор
    +D)Соединение со всеми 40 таблицами

    // разбор: Платформа не знает, какой документ в каждой строке, и строит соединение с таблицей каждого типа из состава. Сорок соединений превращают простой запрос в тяжёлый. Если нужен один тип, пишут ВЫРАЗИТЬ(Р.Регистратор КАК Документ.Реализация).Дата; дата регистратора есть и в поле Период.

дальше

Теорию прочитал. Навык ставится повторением

В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, бесплатная часть практики — одна сессия дня в сутки.