8.2.1.9 Упрощение внешних соединений
Табличные выражения в фрагменте 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 отбрасывает нулевые значения. (То есть, оно преобразует внешнее соединение во внутреннее.) Условие называется отбрасывающим нулевые значения для операции внешнего соединения, если оно вычисляется как FALSE или UNKNOWN для любой строки, дополненной нулевыми значениями, сгенерированной для операции.
Таким образом, для данного внешнего соединения:
T1 LEFT JOIN T2 ON T1.A=T2.A
Условия такого типа отбрасывают нулевые значения, поскольку они не могут быть истинными для любой строки, дополненной нулевыми значениями (со столбцами T2, имеющими значение NULL):
T2.B IS NOT NULL
T2.B > 3
T2.C <= T1.C
T2.B < 2 OR T2.C > 1
Условия такого типа не отбрасывают нулевые значения, потому что они могут быть истинными для строки, дополненной нулевыми значениями:
T2.B IS NULL
T1.B < 3 OR T2.B IS NOT NULL
T1.B < 3 OR T2.B > 3
Общие правила проверки, является ли условие отбрасывающим нулевые значения для операции внешнего соединения, просты:
Оно имеет вид
A IS NOT NULL, гдеA- атрибут любой из внутренних таблицЭто предикат, содержащий ссылку на внутреннюю таблицу, которая вычисляется как
UNKNOWN, когда один из его аргументов являетсяNULLЭто конъюнкция, содержащая условие, отбрасывающее нулевые значения, в качестве конъюнкта
Это дизъюнкция условий, отбрасывающих нулевые значения
Условие может быть отбрасывающим нулевые значения для одной операции внешнего соединения в запросе и не отбрасывающим для другой. В этом запросе условие WHERE отбрасывает нулевые значения для второй операции внешнего соединения, но не для первой:
SELECT * FROM T1 LEFT JOIN T2 ON T2.A=T1.A
LEFT JOIN T3 ON T3.B=T1.B
WHERE T3.C > 0
Если условие WHERE отбрасывает нулевые значения для операции внешнего соединения в запросе, операция внешнего соединения заменяется операцией внутреннего соединения.
Например, в предыдущем запросе второе внешнее соединение отбрасывает нулевые значения и может быть заменено внутренним соединением:
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 отбрасывает нулевые значения. Это приводит к запросу без внешних соединений вообще:
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 не отбрасывает нулевые значения для вложенного внешнего соединения, но условие соединения встраивающего внешнего соединения T2.A=T1.A AND
T3.C=T1.C отбрасывает нулевые значения:
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.