Seleccionar sobre resultados de consulta: CTEs y Subconsultas en el mundo real
Navegar entre tablas relacionales para extraer un indicador limpio suele requerir más que un simple SELECT con un par de JOINs. En el día a día de un consultor de Business Intelligence, es habitual encontrarse con la necesidad de filtrar, agrupar o transformar datos antes de realizar la selección final. Esta técnica, conocida técnicamente como seleccionar sobre los resultados de una consulta, es la base de la modularidad en SQL, pero también es una de las fuentes principales de cuellos de botella si no se entiende qué ocurre bajo el capó.
Cuando trabajamos con SQL Server u Oracle como origen de datos para Power BI o Microsoft Fabric, la forma en la que estructuramos estas consultas anidadas determina si el motor de base de datos podrá optimizar la ejecución o si, por el contrario, acabaremos saturando la memoria del servidor. No se trata solo de que la consulta funcione y devuelva el dato correcto; se trata de que sea sostenible en entornos de producción donde el volumen de datos crece exponencialmente.
En este artículo analizaremos las implicaciones prácticas de utilizar subconsultas y Common Table Expressions (CTEs), cómo afectan al Query Folding en Power Query y qué decisiones de arquitectura debemos tomar para evitar que un informe sencillo termine degradando el rendimiento de todo el sistema.
La anatomía de la selección sobre resultados
Seleccionar sobre un resultado previo significa tratar el output de una consulta como si fuera una tabla física. En SQL, esto se implementa principalmente de tres formas: subconsultas en el FROM (tablas derivadas), CTEs o vistas. Aunque el resultado final pueda parecer idéntico, la forma en que el optimizador de consultas procesa cada una varía significativamente según el motor y la versión.
En un escenario de retail, imaginemos que necesitamos obtener el precio medio de venta por categoría, pero solo para aquellos productos que han tenido movimiento en el último trimestre. Primero necesitamos identificar esos productos y luego calcular la media. Hacerlo todo en un solo bloque plano de código suele ser ilegible. Aquí es donde entra la selección sobre resultados.
-- Ejemplo de Subconsulta (Tabla Derivada)
SELECT
Resumen.Categoria,
AVG(Resumen.ImporteTotal) AS MediaVenta
FROM (
-- Esta es la consulta interna sobre la que seleccionamos
SELECT
P.Categoria,
V.ProductoID,
SUM(V.Importe) AS ImporteTotal
FROM Ventas V
INNER JOIN Producto P ON V.ProductoID = P.ProductoID
WHERE V.Fecha >= '2023-10-01'
GROUP BY P.Categoria, V.ProductoID
) AS Resumen
GROUP BY Resumen.Categoria;El problema de este enfoque es la legibilidad. A medida que añadimos capas, el código se vuelve una «cebolla» difícil de depurar. Para quien ya tiene modelos en producción, el paso lógico es migrar hacia CTEs (Common Table Expressions), que permiten definir la lógica de forma secuencial.
CTEs: ¿Mejoran realmente el rendimiento?
Existe el mito de que las CTEs son inherentemente más rápidas que las subconsultas. La realidad es que, en SQL Server, la mayoría de las veces el optimizador las trata de la misma manera. La ventaja real es la mantenibilidad y la capacidad de reutilización dentro de la misma consulta. Sin embargo, hay un trade-off crítico: en algunas versiones de Oracle o en implementaciones específicas de otros motores, las CTEs pueden actuar como una barrera de optimización (materialización), lo que impide que los predicados externos se filtren hacia la consulta interna.
Desde el punto de vista de un proyecto de Power BI, si estamos usando SQL manual en el conector (Native Query), una CTE compleja puede romper el plegado de consultas en Power Query. Si Power Query no puede «leer» a través de tu lógica anidada, traerá todos los datos a la memoria de la puerta de enlace (Gateway) para aplicar los filtros allí. Esto es un error de arquitectura de manual que vemos constantemente en auditorías.
Comparativa de métodos de anidación
| Método | Legibilidad | Reutilización | Impacto en Rendimiento |
|---|---|---|---|
| Subconsulta (FROM) | Baja | Nula | Generalmente óptimo (optimizador decide) |
| CTE (WITH) | Alta | Dentro de la consulta | Igual a subconsulta (en SQL Server moderno) |
| Vista (VIEW) | Alta | Global | Excelente, permite indexación (Vistas indexadas) |
| Tabla Temporal | Media | Sesión completa | Coste de escritura en TempDB, pero estable |
Impacto en modelos de producción y Power BI
Si eres responsable de un modelo que ya está en producción, la introducción de lógicas de selección sobre resultados debe ir acompañada de una revisión de los planes de ejecución. No es raro ver que una consulta que tarda 2 segundos en SSMS tarda 30 segundos cuando se integra en un informe. Esto ocurre frecuentemente por la falta de estadísticas actualizadas en las tablas involucradas en la consulta interna.
Otro factor determinante es el uso de DirectQuery. En este modo, cada interacción del usuario genera una consulta SQL que a menudo envuelve tu consulta original como una subconsulta adicional. Si tu consulta base ya es una cebolla de tres capas, el motor acabará generando un monstruo de cinco o seis niveles de anidación. Esto es especialmente peligroso en patrones complejos, como explicamos en las 50 Medidas y Patrones DAX que Destruyen el Rendimiento en DirectQuery.
Cuándo evitar la selección sobre resultados
- Si puedes usar un JOIN plano: A veces nos empeñamos en pre-agregar datos en una subconsulta por miedo a duplicar filas, cuando un JOIN bien planteado con un GROUP BY final es más eficiente para el optimizador.
- En transformaciones pesadas de Power Query: Si ya estás haciendo una selección compleja en SQL, evita añadir luego columnas condicionales y personalizadas en Power Query sobre ese resultado si eso va a romper el plegado.
- Filtros dinámicos: Si la consulta interna no puede recibir el filtro de la consulta externa (push-down), estarás procesando millones de filas para luego descartar el 99%.
Estrategias de optimización: Medir antes de actuar
Como consultores, nuestra regla de oro es no tocar una línea de código sin tener una métrica base. En SQL Server, esto implica usar SET STATISTICS IO ON y analizar el plan de ejecución. Debemos buscar operadores de «Spool» que indiquen que el motor está guardando resultados intermedios en TempDB porque la consulta anidada es demasiado compleja para procesarla en streaming.
Si detectas que una selección sobre resultados está penalizando el informe, considera la materialización estratégica. A veces, persistir el resultado de la consulta interna en una tabla real (proceso ETL) o en un Lakehouse de Microsoft Fabric es mucho más eficiente que calcularlo en tiempo de ejecución. Sobre esto, te recomiendo leer nuestra Guía Práctica: Creando un Lakehouse en Microsoft Fabric para entender cómo el almacenamiento en Delta Parquet cambia las reglas del juego.
-- Optimización mediante CTE para evitar duplicidad de lógica
WITH VentasRecientes AS (
SELECT
ProductoID,
SUM(Importe) AS Total
FROM Ventas
WHERE Fecha >= DATEADD(month, -3, GETDATE())
GROUP BY ProductoID
)
SELECT
P.NombreProducto,
VR.Total
FROM Producto P
INNER JOIN VentasRecientes VR ON P.ProductoID = VR.ProductoID
WHERE VR.Total > 1000; -- El filtro se aplica sobre el resultado de la CTEErrores frecuentes que destruyen el rendimiento
- Anidación excesiva: Consultas que seleccionan sobre resultados de otra consulta que a su vez selecciona sobre otra. El optimizador de SQL Server tiene un límite de transformaciones que puede evaluar antes de rendirse y elegir un plan «suficientemente bueno» pero no óptimo.
- Falta de alias claros: No nombrar correctamente las columnas en la subconsulta interna provoca errores de ambigüedad que detienen el refresco de los informes.
- Ordenar dentro de la subconsulta: Usar
ORDER BYdentro de una selección interna es un gasto de recursos inútil, a menos que usesTOPuOFFSET. El orden se pierde al pasar a la consulta externa. - Ignorar el linaje: No saber de dónde viene cada campo cuando seleccionas sobre resultados complejos dificulta la gestión proactiva de errores.
Preguntas frecuentes
¿Es mejor usar una Vista o una CTE?
La vista es preferible si la lógica se va a reutilizar en múltiples informes o por diferentes usuarios. La CTE es mejor para lógica puntual y específica de una sola consulta. A nivel de rendimiento, suelen ser equivalentes, pero la vista permite crear índices (en SQL Server) si la complejidad lo requiere.
¿Por qué mi subconsulta funciona en SQL pero falla en Power BI?
Suele deberse a problemas de permisos o a que el conector de Power BI intenta envolver tu consulta en otra capa de SQL (para aplicar filtros o límites de filas) y se produce un error de sintaxis si no has usado alias para todas las columnas o si la consulta es demasiado compleja para el motor de traducción.
¿Puedo usar parámetros en una selección sobre resultados?
Sí, y es una de las mejores formas de optimizar. Pasar el parámetro de filtro directamente a la consulta interna reduce drásticamente el volumen de datos procesados. Consulta cómo gestionar esto en nuestro artículo sobre tablas de referencia y parámetros en Power Query.
Checklist de revisión para consultas anidadas
- ¿He asignado un alias a todas las tablas derivadas y columnas calculadas?
- ¿He verificado en el Plan de Ejecución que los filtros de la consulta externa se están aplicando (push-down) a la interna?
- ¿Es estrictamente necesaria la subconsulta o podría resolverse con un JOIN y un GROUP BY?
- ¿He evitado el uso de ORDER BY innecesarios en las capas internas?
- ¿Está el Query Folding activo en Power Query tras aplicar esta lógica de SQL?






