Почему объединение вложенных циклов поддерживает только левое соединение?


11

В блоге Крейга Фридмана « Объединение вложенных циклов» он объясняет, почему объединение вложенных циклов не может поддерживать правильное внешнее объединение:

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

Может ли кто-нибудь объяснить это действительно простым и образовательным способом?

Означает ли это, что цикл начинается с внешней таблицы ( R1) и сканирует внутреннюю ( R2)?

Я понимаю, что для R1значения, которое не соединяется с R2, его следует заменить на NULLтак, чтобы результирующий набор стал ( NULL, R2). Для меня кажется невозможным вернуть R2значение, когда R1не присоединяется, по той причине, что он не может знать, какое R2значение вернуть. Но это не так, как это объясняется. Или это?

SQL Server фактически оптимизирует (и часто заменяет) RIGHT JOINна LEFT JOIN, но вопрос в том, чтобы объяснить, почему технически невозможно NESTED LOOPS JOINиспользовать / поддерживать RIGHT JOINлогику.

Ответы:


12

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

Учитывая таблицы A и B, давайте реализуем A LEFT JOIN B.

A
--
1
2

B
_
1
3

Во-первых, давайте сделаем это « естественным » способом.

Перебираем A.
Получаем доступ к записи 1.
Перебираем B.
Находим запись 1 в B и выводим 1-1 .

Мы продолжаем итерацию по A.
Мы обращаемся к записи 2.
Мы итерируем по B.
Мы не находим никакого совпадения в B.
Мы выводим 2-ноль .

Теперь давайте сделаем это « противоположным » способом.

Перебираем B.
Получаем доступ к записи 1.
Перебираем A.
Находим запись 1 в A и выводим 1-1 .

Мы продолжаем итерацию по B.
Мы обращаемся к записи 3.
Мы итерируем по A.
Мы не находим никакого совпадения в A.

Теперь запомните, что это так A LEFT JOIN B, что означает, что в дополнение к 1-1 мы должны вывести 2-ноль .
Проблема в том, что в этот момент мы не знаем, для каких записей с идентификатором A у нас уже есть соответствие (1), а для каких записей нет (2).


На самом деле это может быть решено различными способами, например, путем удержания битового массива для таблицы A.
Когда запись A найдена как совпадение, мы помечаем ее в битовом массиве.
В конце вложенных циклов мы проходим битовый массив, выводим и выводим любую запись, которая не была помечена.
Это, очевидно, сложнее, чем «естественный» вложенный цикл.


13

Что мне не нравится в связанной статье, так это утверждение, что «алгоритм соединения с вложенным циклом не поддерживает оператор логического соединения правого соединения» .

Хотя есть ограничение, формулировка на данный момент немного сбивает с толку. Я надеюсь, что следующее объясняет вещи лучше:

Алгоритм вложенного циклического объединения включает в себя две таблицы (независимо от того, являются ли базовые таблицы или результирующие наборы предыдущих операций несущественными), которые называются внешней и внутренней таблицей и обрабатываются алгоритмом по-разному («внешняя» таблица просматривается во внешней цикл и «внутренняя» таблица на внутренних циклах).

Итак, допустим, у нас есть соединение:

A (some_type) JOIN B

Алгоритм может быть выполнен как:

outer-loop-A  nested-loop  inner-loop-B

или же:

outer-loop-B  nested-loop  inner-loop-A

Теперь, если ( some_type) равно INNERили CROSSобъединить, ограничений нет, планировщик может выбрать один из двух способов (с разными характеристиками производительности, в зависимости от размера наборов, распределения значений объединяемых столбцов, индексов и т. Д.). Обычно самая маленькая таблица выбирается как «внешняя» таблица в алгоритме).

Но когда some_typeэто LEFTсоединение, он может использовать только:

outer-loop-A  nested-loop  inner-loop-B

и нет

outer-loop-B  nested-loop  inner-loop-A

А поскольку a RIGHTвсегда можно переписать как LEFTобъединение, оно имеет то же ограничение в обратном порядке. Для A RIGHT JOIN B(который может быть переписан B LEFT JOIN A) он может использовать только:

outer-loop-B  nested-loop  inner-loop-A

и не наоборот * .

То же ограничение применяется к левому полусоединению, левому полусоединению, правому полусоединению и правому полусоединению.

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

* Как объясняет Дуду Марковиц в своем ответе, можно было бы использовать обратный путь, но только если мы изменили алгоритм соединения с вложенным циклом, чтобы иметь дополнительную структуру и дополнительный шаг в конце.


Ну, это многое прояснило. Ваш ответ в сочетании с Dudu M: s объясняет это очень хорошо!
GordonLiddy
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.