É possível ter algum controle sobre o planejador de comandos utilizando a sintaxe JOIN explícita. Para saber por que isto tem importância, primeiro é necessário ter algum conhecimento.
Em uma consulta de junção simples, como
SELECT * FROM a, b, c WHERE a.id = b.id AND b.ref = c.id;
o planejador está livre para fazer a junção das tabelas em qualquer ordem. Por exemplo, pode gerar um plano de comando que faz a junção de A com B utilizando a condição a.id = b.id do WHERE e, depois, fazer a junção de C com a tabela juntada utilizando a outra condição do WHERE. Poderia, também, fazer a junção de B com C e depois juntar A ao resultado. Ou, também, fazer a junção de A com C e depois juntar B, mas isto não seria eficiente, uma vez que deveria ser produzido o produto Cartesiano completo de A com C, porque não existe nenhuma condição aplicável na cláusula WHERE que permita a otimização desta junção (Todas as junções no executor do PostgreSQL acontecem entre duas tabelas de entrada sendo, portanto, necessário construir o resultado a partir de uma ou outra destas formas). O ponto a ser destacado é que estas diferentes possibilidades de junção produzem resultados semanticamente equivalentes, mas podem ter custos de execução muito diferentes. Portanto, o planejador explora todas as possibilidades tentando encontrar o plano de consulta mais eficiente.
Quando uma consulta envolve apenas duas ou três tabelas não existem muitas ordens de junção com que se preocupar. Porém, o número de ordens de junção possíveis cresce exponencialmente quando o número de tabelas aumenta. Acima de dez tabelas de entrada não é mais prático efetuar uma procura exaustiva por todas as possibilidades, e mesmo para seis ou sete tabelas o planejamento pode levar um longo tempo. Quando existem muitas tabelas de entrada, o planejador do PostgreSQL pode alternar da procura exaustiva para a procura probabilística genética através de um número limitado de possibilidades (O ponto de mudança é definido pelo parâmetro em tempo de execução geqo_threshold). A procura genética leva menos tempo, mas não encontra necessariamente o melhor plano possível.
Quando a consulta envolve junções externas, o planejador tem muito menos liberdade que com as junções puras (internas). Por exemplo, considere:
SELECT * FROM a LEFT JOIN (b JOIN c ON (b.ref = c.id)) ON (a.id = b.id);
Embora as restrições desta consulta sejam superficialmente semelhantes às do exemplo anterior as semânticas são diferentes, porque deve ser gerada uma linha para cada linha de A que não possua linha correspondente na junção de B com C. Portanto, o planejador não pode escolher a ordem de junção neste caso: deve fazer a junção de B com C e depois juntar A ao resultado. Portanto, esta consulta leva menos tempo para ser planejada que a consulta anterior.
A sintaxe de junção interna explícita (INNER JOIN, CROSS JOIN ou JOIN sem adornos) é semanticamente idêntica a listar as relações de entrada na cláusula FROM, portanto não precisa restringir a ordem de junção. Ainda assim é possível instruir o planejador de comandos do PostgreSQL para que trate os JOINs internos explícitos como restringindo a ordem de junção. Por exemplo, estas três consultas são logicamente equivalentes:
SELECT * FROM a, b, c WHERE a.id = b.id AND b.ref = c.id; SELECT * FROM a CROSS JOIN b CROSS JOIN c WHERE a.id = b.id AND b.ref = c.id; SELECT * FROM a JOIN (b JOIN c ON (b.ref = c.id)) ON (a.id = b.id);
Mas se dissermos ao planejador para que respeite a ordem do JOIN, a segunda e a terceira consultas levam menos tempo para serem planejadas que a primeira. Não vale a pena se preocupar com este efeito para apenas três tabelas, mas pode ser de grande valia para muitas tabelas.
Para obrigar o planejador seguir a ordem do JOIN para as junções internas, deve ser definido o parâmetro em tempo de execução join_collapse_limit como 1 (São mostrados abaixo outros valores possíveis).
Não é necessário restringir completamente a ordem de junção para diminuir o tempo de procura, porque podem ser utilizados operadores JOIN entre os itens da lista FROM pura. Por exemplo,
SELECT * FROM a CROSS JOIN b, c, d, e WHERE ...;
Com join_collapse_limit = 1 isto obriga o planejador a fazer a junção de A com B antes de juntá-las às outras tabelas, mas não restringe suas escolhas além disso. Neste exemplo, o número de ordens de junção possíveis é reduzido por um fator de 5.
Restringir a procura do planejador desta maneira é uma técnica útil tanto para reduzir o tempo de planejamento quanto para direcionar o planejador para um bom plano de comando. Se o planejador escolher uma ordem de junção ruim por padrão, é possível forçá-lo a escolher uma ordem melhor por meio da sintaxe do JOIN — assumindo que se conheça uma ordem melhor. Recomenda-se realizar experiências.
Uma questão intimamente relacionada, que afeta o tempo de planejamento, é o colapso das subconsultas dentro da consulta ancestral. Por exemplo, considere
SELECT * FROM x, y, (SELECT * FROM a, b, c WHERE alguma_coisa) AS ss WHERE uma_outra_coisa;
Esta situação pode ocorrer quando se utiliza uma visão contendo uma junção; a regra SELECT da visão é inserida no lugar da referência à visão, produzindo uma consulta muito parecida com a mostrada acima. Normalmente, o planejador tenta fazer o colapso da subconsulta dentro da consulta externa, produzindo
SELECT * FROM x, y, a, b, c WHERE alguma_coisa AND uma_outra_coisa;
Geralmente isto resulta em um plano melhor que planejar a subconsulta separadamente (Por exemplo, as condições do WHERE externo podem ser tais que a junção de X com A primeiro elimine muitas linhas de A evitando, portanto, a necessidade de formar a saída lógica completa da subconsulta). Mas ao mesmo tempo foi aumentado o tempo de planejamento; agora temos um problema de junção de cinco vias no lugar de dois problemas de junção de três vias separados. Devido ao crescimento exponencial do número de possibilidades, isto faz uma grande diferença. O planejador tenta evitar ficar preso a problemas de procura de junção grande não fazendo o colapso da subconsulta se resultar em mais que from_collapse_limit itens na cláusula FROM da consulta externa. É possível equilibrar o tempo de planejamento versus a qualidade do plano ajustando este parâmetro de tempo de execução para cima ou para baixo.
from_collapse_limit e join_collapse_limit possuem nomes semelhantes porque fazem praticamente a mesma coisa: um controla quando o planejador irá "aplanar" (flatten out) as subconsultas, e o outro controla quando serão aplanadas as junções internas explícitas. Normalmente join_collapse_limit é definido igual a from_collapse_limit (fazendo as junções explícitas e as subconsultas agirem da mesma maneira), ou join_collapse_limit é definido igual a 1 (se for desejado controlar a ordem de junção com junções explícitas). Mas podem ser definidas com valores diferentes quando se tenta aprimorar o equilíbrio entre o tempo de planejamento e o tempo de execução.