La Consulta SQL
Una consulta SQL parece una oración simple, pero en realidad es una especificación declarativa de un conjunto de resultados que PostgreSQL puede calcular libremente de la manera más rápida.
Busca en todas las páginas de la documentación
Una consulta SQL parece una oración simple, pero en realidad es una especificación declarativa de un conjunto de resultados que PostgreSQL puede calcular libremente de la manera más rápida.
Esta página construye el modelo mental de cómo SQL es leído, reordenado y ejecutado por PostgreSQL, lo que explica por qué WHERE y HAVING se comportan de manera diferente, por qué el orden escrito no es el orden de ejecución y por qué el mismo resultado puede provenir de planes muy distintos.
EXPLAIN te indica el plan físico real.SQL es un lenguaje declarativo, lo que significa que describes la forma del resultado que deseas en lugar de los pasos para producirlo.
Esto es lo opuesto a un lenguaje imperativo como un bucle en el código de una aplicación, donde controlas cada paso explícitamente.
Debido a que SQL es declarativo, el planificador de PostgreSQL es libre de elegir escaneos, JOINs y ordenaciones que no aparecen en ningún lugar del texto de tu consulta.
Las raíces de SQL se encuentran en el álgebra relacional, donde una consulta es una secuencia de operaciones (selección, proyección, JOIN, agregación) aplicadas a conjuntos de filas llamados relaciones.
Una tabla es una relación, una fila es una tupla y una columna es un atributo, aunque el SQL de uso diario rara vez utiliza esa terminología.
Las cláusulas que escribes, SELECT, FROM, WHERE, GROUP BY, HAVING, ORDER BY y LIMIT, se mapean a ese álgebra relacional, aunque las escribas en un orden diferente al que se ejecutan lógicamente.
El orden lógico de procesamiento comienza con FROM y cualquier JOIN, luego WHERE filtra las filas, luego GROUP BY las agrupa, luego HAVING filtra los grupos, luego SELECT calcula las columnas finales, luego ORDER BY ordena y finalmente LIMIT recorta el resultado.
Ese orden lógico explica por qué no puedes referenciar un alias de SELECT dentro de la cláusula WHERE de la misma consulta, pero sí puedes referenciarlo en ORDER BY, ya que WHERE se ejecuta antes que SELECT y ORDER BY se ejecuta después.
PostgreSQL nunca ejecuta las cláusulas en ese orden lógico literalmente, ya que el planificador es libre de reordenar las operaciones siempre que el resultado final coincida con la especificación lógica.
escrito: SELECT -> FROM -> WHERE -> GROUP BY -> HAVING -> ORDER BY -> LIMIT
lógico: FROM/JOIN -> WHERE -> GROUP BY -> HAVING -> SELECT -> ORDER BY -> LIMIT
Ver ambos órdenes uno al lado del otro explica la mayor parte del comportamiento sorprendente en SQL, desde el ámbito de los alias hasta por qué una cláusula HAVING puede referenciar un agregado que WHERE no puede.
El planificador convierte tu consulta en un árbol de operadores físicos, decidiendo cosas como si escanear una tabla secuencialmente o a través de un índice, y en qué orden unir múltiples tablas.
Esas decisiones se basan en estadísticas que PostgreSQL mantiene sobre el tamaño de la tabla y la distribución de los valores de las columnas, que ANALYZE actualiza y el autovacuum activa automáticamente.
Un predicado se denomina sargable cuando el planificador puede usar un índice para satisfacerlo directamente, y envolver una columna indexada en una función generalmente rompe eso, forzando en su lugar un escaneo completo.
NULL añade un tercer valor lógico, desconocido, a cada comparación, por lo que WHERE column = NULL nunca coincide con nada y IS NULL existe como un operador separado.
Las operaciones de conjuntos como UNION, INTERSECT y EXCEPT combinan los resultados de dos consultas como conjuntos completos en lugar de unirlas fila por fila.
Las subconsultas te permiten anidar el resultado lógico de una consulta dentro de otra, ya sea como un valor escalar, una fuente de filas en FROM o una prueba booleana con EXISTS.
Las subconsultas correlacionadas hacen referencia a las columnas de la consulta externa y conceptualmente se re-ejecutan por cada fila externa, aunque el planificador a menudo las reescribe internamente en un plan similar a un JOIN.
El mismo resultado lógico a menudo se puede expresar como una subconsulta correlacionada, un JOIN, una función de ventana o una expresión de tabla común (CTE), y el planificador de PostgreSQL no siempre produce el mismo plan físico para cada forma, incluso cuando las filas de salida son idénticas.
Las expresiones de tabla comunes definidas con WITH actuaban como una barrera de optimización antes de PostgreSQL 12, pero a partir de PostgreSQL 12, y sigue siendo cierto en la versión 18.4, el planificador inserta CTEs no recursivas a menos que marques una como MATERIALIZED o la referencies más de una vez.
Las funciones de ventana calculan un valor similar a un agregado por fila sin colapsar las filas como lo hace GROUP BY, lo que las convierte en la herramienta adecuada para totales acumulados, clasificaciones y promedios móviles.
EXISTS e IN a menudo producen resultados equivalentes para pruebas de pertenencia, pero EXISTS se detiene en la primera coincidencia y maneja los NULLs de manera más predecible que NOT IN, que puede devolver silenciosamente ninguna fila si la subconsulta produce algún NULL.
La legibilidad de la consulta y el rendimiento de la consulta no son el mismo eje, por lo que refactorizar una subconsulta anidada en una CTE para mayor claridad no debe asumirse que cambie el plan de ejecución.
| Enfoque | Fortaleza | Debilidad | Mejor Ajuste |
|---|---|---|---|
| Subconsulta correlacionada | Se lee naturalmente de arriba a abajo, buena para un único valor derivado | Puede forzar la reevaluación por fila si el planificador no puede reescribirla | Pequeñas búsquedas, pantallas de administración |
| JOIN | Permite al planificador elegir libremente el orden y el algoritmo del JOIN | Duplica filas en relaciones de uno a muchos a menos que se agreguen | Combinar datos de múltiples tablas a escala |
| Función de ventana | Mantiene el detalle a nivel de fila mientras agrega contexto agregado | Sintaxis menos familiar, fácil de usar incorrectamente las cláusulas de marco | Clasificaciones, totales acumulados, comparaciones por grupo |
| Expresión de tabla común (CTE) | Nombra un paso para legibilidad y reutilización dentro de una consulta | Ya no garantiza la materialización a menos que se solicite | Dividir una consulta compleja en etapas legibles |
La elección entre estas formas debe comenzar por lo que la consulta necesita expresar, luego verificarse con EXPLAIN, no decidirse solo por hábito.
WHERE filtra las filas antes de que SELECT calcule las columnas de salida, razón por la cual un alias de SELECT no puede usarse dentro de la cláusula WHERE de esa misma consulta.NULL = NULL se evalúa como desconocido en lugar de verdadero, y solo IS NULL lo prueba de manera confiable.GROUP BY está diseñado para emparejarse con funciones de agregado por grupo.Porque lógicamente WHERE se ejecuta antes de que SELECT calcule sus columnas de salida, el alias aún no existe en el punto en que se evalúa WHERE.
FROM/JOINs, luego WHERE, luego GROUP BY, luego HAVING, luego SELECT, luego ORDER BY, luego LIMIT, y el orden escrito solo coincide con esta secuencia para FROM y WHERE.
NULL significa desconocido, comparar cualquier cosa con desconocido produce desconocido en lugar de verdadero, y IS NULL o IS NOT NULL es la forma correcta de probarlo en su lugar.
No necesariamente, ya que el planificador a menudo reescribe subconsultas correlacionadas en planes equivalentes a JOINs, por lo que la elección debe basarse primero en la legibilidad y la corrección, y luego verificarse con EXPLAIN.
WHERE filtra filas individuales antes de que ocurra la agrupación.
HAVING filtra grupos después de que GROUP BY ha colapsado las filas, por lo que puede referenciar funciones de agregado que WHERE no puede.
No necesariamente, ya que PostgreSQL 12 inserta CTEs no recursivas en la consulta circundante por defecto, a menos que una esté marcada como MATERIALIZED o se referencie más de una vez.
La elección del planificador depende de las estadísticas de la tabla, que cambian a medida que crecen los datos o se ejecuta ANALYZE, por lo que el mismo texto SQL puede producir un plan diferente con el tiempo.
Un predicado sargable es aquel que el planificador puede satisfacer directamente con un índice, y envolver una columna indexada en una función o cast generalmente hace que el predicado no sea sargable, forzando en su lugar un escaneo completo.
Usa una función de ventana cuando necesites un valor similar a un agregado, como un rango o un total acumulado, junto con el detalle de fila original en lugar de colapsar las filas.
UNION elimina filas duplicadas en el resultado combinado, lo que requiere un paso de ordenación o hash.
UNION ALL mantiene todas las filas y es más económico cuando los duplicados no son una preocupación.
EXISTS puede detenerse en la primera fila coincidente y maneja los NULLs en la subconsulta de manera más predecible que NOT IN, que puede devolver silenciosamente ninguna fila si la subconsulta produce algún NULL.
No, ya que las mayúsculas de las palabras clave, los saltos de línea y la indentación son puramente para la legibilidad humana y no tienen ningún efecto en la consulta analizada ni en el plan elegido.
Ejecuta EXPLAIN (ANALYZE, BUFFERS) contra la consulta y busca un nodo de escaneo de índice o escaneo solo de índice en lugar de un escaneo secuencial.
Versiones de Stack: Esta página fue escrita para PostgreSQL 18.4 (estable 18, mantenimiento 17); el comportamiento de inserción de CTE descrito se aplica desde PostgreSQL 12 en adelante.
Revisado por Chris St. John·Última actualización: 19 jul 2026