Чому вкладені петлі приєднуються лише до підтримуваних лівих з'єднань?


11

У блозі Крейга Фрідмана, Nested Loops Join , він пояснює, чому вкладені петлі з'єднання не можуть підтримувати правильне зовнішнє з'єднання:

Проблема полягає в тому, що ми скануємо внутрішню таблицю кілька разів - один раз для кожного ряду зовнішнього з'єднання. Під час цих кількох сканувань ми можемо зустріти одні й ті ж внутрішні рядки кілька разів. На якому моменті можна зробити висновок, що певний внутрішній ряд не приєднується чи не буде?

Чи може хтось, будь ласка, пояснити це по-справжньому простим та навчальним способом?

Чи означає це, що цикл починається з зовнішньої таблиці ( R1) і сканує внутрішню ( R2)?

Я розумію, що для R1значення, яке не поєднується R2, його слід замінити NULLтаким чином, набір результатів стає ( NULL, R2). Мені здається неможливим повернути R2значення, коли R1воно не приєднується, з тієї причини, що він не може знати, яке R2значення повернути. Але це не так, як це пояснюється. Або це?

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

Відповіді:


12

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

Давши таблиці А і В, реалізуємо A LEFT JOIN B.

A
--
1
2

B
_
1
3

Спочатку давайте зробимо це " природним " способом.

Ми повторюємо через A. Ми отримуємо
доступ до запису 1.
Ми повторюємо через B.
Ми знаходимо запис 1 у B та вихід 1-1 .

Ми продовжуємо ітерацію через A.
Ми отримуємо доступ до запису 2.
Ми повторюємо через B.
Ми не знаходимо жодної відповідності в B.
Виводимо 2-null .

Тепер давайте зробимо це " навпаки ".

Ми повторюємо через B. Ми отримуємо
доступ до запису 1.
Ми повторюємо через A.
Ми знаходимо запис 1 в A і вихід 1-1 .

Ми продовжуємо повторювати через B.
Ми отримуємо доступ до запису 3.
Ми повторюємо через A.
Ми не знаходимо жодної відповідності в A.

Тепер пам’ятайте, що це було A LEFT JOIN B, а значить, крім 1-1, ми повинні виводити 2-null .
Проблема полягає в тому, що в цей момент ми не маємо уявлення, для яких записів id A у нас вже є збіг (1) і для яких записів у нас немає (2).


Це насправді можна вирішити різними способами, наприклад, тримаючи бітовий масив для таблиці А.
Коли запис A знайдений як збіг, ми позначаємо його у бітовому масиві.
В кінці вкладених циклів ми проходимо через бітовий масив і виводимо та виводимо будь-яку запис, яка не була позначена.
Це, очевидно, складніше, ніж "природний" вкладений цикл.


13

Що мені не подобається у пов'язаній статті - це твердження про те, що "алгоритм об'єднання вкладених циклів не підтримує оператора логічного об'єднання права приєднання" .

Хоча є обмеження, формулювання в цьому пункті трохи заплутане. Я сподіваюся, що наступне пояснює щось краще:

Вкладений алгоритм приєднання lop включає дві таблиці (незалежно від того, чи є базові таблиці чи набори результатів попередніх операцій), які називаються зовнішньою та внутрішньою таблицями, і алгоритмом вони розглядаються по-різному ("зовнішня" таблиця проходить на зовнішній петля і «внутрішня» таблиця при внутрішніх петлях).

Отже, скажімо, у нас є приєднання:

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(що можна переписати a 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.