La tarea del planificador/optimizador es crear un plan de ejecución óptimo. Primero combina todas las posibles vías de barrer (scannear) y cruzar (join) las relaciones que aparecen en una consulta. Todas las rutas creadas conducen al mismo resultado y es el trabajo del optimizador estimar el coste de ejecutar cada una de ellas para encontrar cual es la más económica.
El planificador/optimizador decide qué planes deberían generarse basándose en los tipos de índices definidos sobre las relaciones que aparecen en una consulta. Siempre existe la posibilidad de realizar un barrido secuencial de una relación, de modo que siempre se crea un plan que sólo utiliza barridos secuenciales. Se asume que hay definido un índice en una relación (por ejemplo un índice B-tree) y una consulta contiene la restricción relation.attribute OPR constant. Si relation.attribute acierta a coincidir con la clave del índice B-tree y OPR es distinto de '<>' se crea un plan utilizando el índice B-tree para barrer la relación. Si hay otros índices presentes y las restricciones de la consulta aciertan con una clave de un índice, se considerarán otros planes.
Tras encontrar todos los planes utilizables para revisar relaciones únicas, se crean los planes para cruzar (join) relaciones. El planificador/optimizador considera sólo cruces entre cada dos relaciones para los cuales existe una cláusula de cruce correspondiente (es decir, para las cuales existe una restricción como WHERE rel1.attr1=rel2.attr2) en la cualificación de la WHERE. Se generan todos los posibles planes para cada cruce considerado por el planificador/optimizador. Las tes posibles estrategias son:
Cruce de iteración anidada (nested iteration join): La relación derecha se recorre para cada tupla encontrada en la relación izquierda. Esta estrategia es fácil de implementar pero puede consumir mucho tiempo.
Cruce de ordenación mezclada (merge sort join): Cada relación es ordenada por los atributos del cruce antes de iniciar el cruce mismo. Después se mezclan las dos relaciones teniendo en cuenta que ambas relaciones están ordenadas pro los atributos del cruce. Este modelo de cruce es más atractivo porque cada relación debe ser barrida sólo una vez.
Cruce indexado (hash join): La relación de la derecha se indexa primero sobre sus atributos para el cruce. A continuación, se barre la relación izquierda, y los valores apropiados de cada tupla encontrada se utilizan como clave indexada para localizar las tuplas de la relación derecha.
Daremos ahora una pequeña descripción de los nodos que aparecen en el plan. La figura \ref{plan} muestra el plan producido para la consulta del ejemplo \ref{simple_select}.
El nodo superior del plan es un nodo Cruce Mezclado (MergeJoin) que tiene dos sucesores, uno unido al campo árbol izquierdo (lefttree) y el segundo unido al campo árbol derecho (righttree). Cada uno de los subnodos representa una relación del cruce. Como se mencionó antes, un cruce de mezcla ordenada requiere que cada relación sea ordenada. Por ello encontramos un nodo Sort en cada subplan. La cualificación adicional dada en la consulta (s.sno > 2) se envía tan lejos como es posible y se une al campo qpqual de la rama SeqScan del nodo del correspondiente subplan.
La lista unida al campo mergeclauses del nodo Cruce Mezclado (MergeJoin) contiene información sobre los atributos de cruce. Los valores 65000 y 65001 de los campos varno y los nodos VAR que aparecen en la lista mergeclauses (y también en la lista objetivo) muestran que las tuplas del nodo actual no deben ser consideradas, sino que se deben utilizar en su lugar las tuplas de los siguientes nodos "más profundos" (es decir, los nodos superiores de los subplanes).
Nótese que todos los nodos Sort y SeqScan que aparecen en la figura \ref{plan} han tomado una lista objetivo, pero debido a la falta de espacio sólo se ha dibujado el correspondiente al Cruce Mezclado.
Otra tarea realizada por el planificador/optimizador es fijar los identificadores de operador en los nodos Expr y Oper. Como se mencionó anteriormente, Postgres soporta una variedad de tipos diferentes de datos, e incluso se pueden utilizar tipos definidos por el usuario. Para ser capaz de mantener la gran cantidad de funciones y operadores, es necesario almacenarlos en una tabla del sistema. Cada función y operador toma un identificador de operador único. De acuerdo con los tipos de los atributos usados en las cualificaciones, etc, se utilizan los identificadores de operador apropiados.