10.2.1.10 Упрощение внешних соединений
Выражения таблиц в фрагменте FROM запроса упрощаются во многих случаях.
На стадии парсинга запросы с операциями правого внешнего соединения преобразуются в эквивалентные запросы, содержащие только левые соединения. В общем случае преобразование выполняется таким образом, что это правое соединение:
(T1, ...) RIGHT JOIN (T2, ...) ON P(T1, ..., T2, ...)
Преобразуется в это эквивалентное левое соединение:
(T2, ...) LEFT JOIN (T1, ...) ON P(T1, ..., T2, ...)
Все выражения внутреннего соединения вида T1 INNER JOIN
T2 ON P(T1,T2) заменяются списком T1,T2, P(T1,T2), объединенными как конъюнкция с условием WHERE (или с условием соединения встраиваемого соединения, если таковое имеется).
При оценке планов оптимизатором для операций внешних соединений учитываются только планы, где для каждой такой операции внешние таблицы обращаются до внутренних таблиц. Выбор оптимизатора ограничен, поскольку только такие планы позволяют выполнять внешние соединения с помощью алгоритма вложенных циклов.
Рассмотрим запрос такого вида, где R(T2) значительно сужает количество совпадающих строк из таблицы T2:
SELECT * T1 FROM T1
LEFT JOIN T2 ON P1(T1,T2)
WHERE P(T1,T2) AND R(T2)
Если запрос выполняется в написанном виде, оптимизатор не имеет выбора, кроме как обратиться к менее ограниченной таблице T1 до более ограниченной таблицы T2, что может привести к очень неэффективному плану выполнения.
Вместо этого MySQL преобразует запрос в запрос без операции внешнего соединения, если условие WHERE отбрасывает значения NULL. (То есть, оно преобразует внешнее соединение во внутреннее). Условие считается отбрасывающим значения NULL для операции внешнего соединения, если оно вычисляется как FALSE или UNKNOWN для любой строки, сгенерированной для операции с дополнением по NULL.
Таким образом, для этого внешнего соединения:
T1 LEFT JOIN T2 ON T1.A=T2.A
Условия, такие как эти, отбрасывают значения NULL, потому что они не могут быть истинными для любой строки с дополнением по NULL (со столбцами T2, имеющими значение NULL):
T2.B IS NOT NULL
T2.B > 3
T2.C <= T1.C
T2.B < 2 OR T2.C > 1
Условия, такие как эти, не отбрасывают значения NULL, потому что они могут быть истинными для строки с дополнением по NULL:
T2.B IS NULL
T1.B < 3 OR T2.B IS NOT NULL
T1.B < 3 OR T2.B > 3
Общие правила проверки того, отбрасывает ли условие значения NULL для операции внешнего соединения, просты:
Оно имеет вид
A IS NOT NULL, гдеA— атрибут любой из внутренних таблицЭто предикат, содержащий ссылку на внутреннюю таблицу, которая вычисляется как
UNKNOWN, когда один из его аргументов —NULLЭто конъюнкция, содержащая условие, отбрасывающее значения NULL, как конъюнкцию
Это дизъюнкция условий, отбрасывающих значения NULL
Условие может быть отбрасывающим значения NULL для одной операции внешнего соединения в запросе и не отбрасывающим значения NULL для другой. В этом запросе условие WHERE отбрасывает значения NULL для второй операции внешнего соединения, но не отбрасывает их для первой:
SELECT * FROM T1 LEFT JOIN T2 ON T2.A=T1.A
LEFT JOIN T3 ON T3.B=T1.B
WHERE T3.C > 0
Если условие WHERE отбрасывает значения NULL для операции внешнего соединения в запросе, операция внешнего соединения заменяется операцией внутреннего соединения.
Например, в предыдущем запросе второе внешнее соединение отбрасывает значения NULL и может быть заменено внутренним соединением:
SELECT * FROM T1 LEFT JOIN T2 ON T2.A=T1.A
INNER JOIN T3 ON T3.B=T1.B
WHERE T3.C > 0
Для исходного запроса оптимизатор оценивает только планы, совместимые с порядком доступа к одной таблице T1,T2,T3. Для переписанного запроса он дополнительно рассматривает порядок доступа T3,T1,T2.
Преобразование одной операции внешнего соединения может вызвать преобразование другой. Таким образом, запрос:
SELECT * FROM T1 LEFT JOIN T2 ON T2.A=T1.A
LEFT JOIN T3 ON T3.B=T2.B
WHERE T3.C > 0
Сначала преобразуется в запрос:
SELECT * FROM T1 LEFT JOIN T2 ON T2.A=T1.A
INNER JOIN T3 ON T3.B=T2.B
WHERE T3.C > 0
Что эквивалентно запросу:
SELECT * FROM (T1 LEFT JOIN T2 ON T2.A=T1.A), T3
WHERE T3.C > 0 AND T3.B=T2.B
Остающуюся операцию внешнего соединения также можно заменить внутренней, так как условие T3.B=T2.B отбрасывает значения NULL. Это приводит к запросу без внешних соединений вообще:
SELECT * FROM (T1 INNER JOIN T2 ON T2.A=T1.A), T3
WHERE T3.C > 0 AND T3.B=T2.B
Иногда оптимизатор успешно заменяет вложенную операцию внешнего соединения, но не может преобразовать встраиваемое внешнее соединение. Следующий запрос:
SELECT * FROM T1 LEFT JOIN
(T2 LEFT JOIN T3 ON T3.B=T2.B)
ON T2.A=T1.A
WHERE T3.C > 0
Преобразуется в:
SELECT * FROM T1 LEFT JOIN
(T2 INNER JOIN T3 ON T3.B=T2.B)
ON T2.A=T1.A
WHERE T3.C > 0
Что может быть переписано только в форме, все еще содержащей операцию встраиваемого внешнего соединения:
SELECT * FROM T1 LEFT JOIN
(T2,T3)
ON (T2.A=T1.A AND T3.B=T2.B)
WHERE T3.C > 0
Любая попытка преобразовать вложенную операцию внешнего соединения в запросе должна учитывать условие соединения для встраиваемого внешнего соединения вместе с условием WHERE. В этом запросе условие WHERE не отбрасывает значения NULL для вложенного внешнего соединения, но условие соединения встраиваемого внешнего соединения T2.A=T1.A AND
T3.C=T1.C отбрасывает значения NULL:
SELECT * FROM T1 LEFT JOIN
(T2 LEFT JOIN T3 ON T3.B=T2.B)
ON T2.A=T1.A AND T3.C=T1.C
WHERE T3.D > 0 OR T1.D > 0
Следовательно, запрос можно преобразовать в:
SELECT * FROM T1 LEFT JOIN
(T2, T3)
ON T2.A=T1.A AND T3.C=T1.C AND T3.B=T2.B
WHERE T3.D > 0 OR T1.D > 0
© 2025 Oracle
Licensed under the GPLv2 License.