Agregación y Vistas en PostgreSQL
Hasta ahora cada fila del resultado era una fila real de una tabla. Pero muchas preguntas de negocio piden resúmenes: cuánto se vendió por categoría, cuántas ventas hizo cada vendedor. GROUP BY agrupa filas para calcular esos resúmenes, y las vistas le ponen nombre a una consulta que vas a repetir una y otra vez.
👁 — vistas · Ing. Roy Carrasco, Facultad de Ingeniería de Sistemas · UAB
Scrollea para empezar — a la izquierda vas a ver la consulta y su resultado armarse solos, mientras leés a la derecha.
-- todavía no escribimos nada
Una tabla llena de ventas sueltas
La tabla ventas tiene una fila por cada venta individual: quién vendió, en qué categoría y por cuánto. El gerente no quiere ver las 8 filas sueltas — quiere saber cuánto se vendió por categoría.
Ninguna fila de la tabla contesta esa pregunta por sí sola.
Todavía sin resumir nada
Elegimos las columnas que importan y miramos la tabla tal cual está: cada fila sigue siendo una venta individual, sin agrupar.
Una función que colapsa muchas filas en una
COUNT y SUM son funciones de agregación: toman muchas filas y devuelven un solo valor. Pero sin nada más, colapsan toda la tabla en una única fila — perdemos la distinción por categoría.
Un grupo por cada categoría
GROUP BY categoria arma un grupo por cada valor distinto de esa columna, y calcula las funciones de agregación dentro de cada grupo, no sobre toda la tabla.
Ahora sí: tres filas, una por categoría.
Varias funciones agregadas a la vez
Agregar AVG(monto) no cambia cuántos grupos hay — solo suma una columna calculada más, por el mismo grupo.
Filtrar los grupos, no las filas
HAVING filtra después de agrupar, sobre valores ya calculados como SUM(monto). Por eso puede comparar contra un total agregado — algo que WHERE no podría hacer.
Ropa quedó afuera: su total (Bs 210) no supera el filtro.
Cada filtro en su momento
WHERE descarta filas antes de agrupar — acá, ventas de Bs 100 o menos. Recién con lo que queda se arma el GROUP BY, y HAVING decide qué grupos sobreviven.
Ropa y Hogar perdieron sus ventas chicas y se quedaron con un solo grupo cada uno; el HAVING los descarta.
Quedarnos con las mejores categorías
Igual que con filas comunes, el resultado ya agregado también se puede ordenar y recortar. Ordenamos por total de mayor a menor y nos quedamos con las 2 categorías que más vendieron.
Guardate esta idea: GROUP BY resume, HAVING filtra esos resúmenes, y WHERE, ORDER BY y LIMIT siguen funcionando igual que siempre.
Practicá lo que viste
Antes de seguir a vistas, confirmá que GROUP BY y HAVING quedaron claros.
Una tabla ventas tiene 8 filas repartidas en 3 categorías distintas. ¿Cuántas filas devuelve SELECT categoria, COUNT(*) FROM ventas GROUP BY categoria;?
Sobre la consulta que armaste arriba, en su versión con GROUP BY y HAVING.
1. La cláusula que filtra los grupos ya calculados (por ejemplo, solo categorías con más de 2 ventas) es .
2. Si quisieras filtrar filas individuales antes de agruparlas (por ejemplo, ignorar ventas menores a Bs 100), usarías .
Clasifica cada fragmento de consulta según qué hace.
-- todavía no escribimos nada
Repetir la misma consulta larga, otra vez
Cada vez que alguien pide el resumen por categoría (cuántas ventas, total, promedio), hay que volver a escribir la misma consulta con GROUP BY completa. ¿Y si le ponemos un nombre y la guardamos?
Guardar la consulta, no los datos
CREATE VIEW guarda esta consulta bajo un nombre. Ojo: no copia ni duplica ningún dato — guarda el SELECT tal cual, y lo vuelve a ejecutar cada vez que alguien consulta la vista.
Se usa como si fuera una tabla
SELECT * FROM resumen_categorias se escribe igual que contra cualquier tabla — y el resultado es exactamente el mismo que la consulta larga de GROUP BY, con muchísima menos sintaxis para recordar.
WHERE, no HAVING, sobre la vista
Para la vista, total ya es una columna común como cualquier otra — no una función de agregación a medio calcular. Por eso acá se filtra con WHERE, no con HAVING.
Actualizar la vista sin borrarla
CREATE OR REPLACE VIEW reemplaza la definición sin eliminar la vista. Cualquier consulta que ya la usaba (como la del paso anterior) sigue funcionando igual, ahora con una columna nueva disponible.
Practicá lo que viste
Emparejá cada operación con lo que hace, y confirmá qué pasa realmente con los datos al crear una vista.
Une cada operación con su descripción.
¿Qué pasa con los datos de la tabla ventas cuando se ejecuta CREATE VIEW resumen_categorias AS SELECT ...?
Ejercicios de razonamiento
En estas preguntas no se te pide recordar sintaxis: se te da un escenario, y debés razonar cuál sería la respuesta correcta antes de comprobarla.
El gerente quiere el promedio de venta de cada vendedor, pero solo de los vendedores que hicieron 2 o más ventas.
¿Qué necesitás para excluir a los vendedores con una sola venta?
Necesitás el total vendido por cada vendedor, pero ignorando las ventas menores a Bs 100 (se consideran "muestras", no ventas reales).
¿En qué orden se aplican los filtros en esa consulta?
Un colega armó esta vista: CREATE VIEW top_categorias AS SELECT categoria, SUM(monto) AS total FROM ventas GROUP BY categoria ORDER BY total DESC LIMIT 2; y ahora ejecuta SELECT * FROM top_categorias;
Completa: el resultado que devuelve esa consulta es
.
Actividades de repaso
Un repaso integrador de todo el módulo. Cada actividad se corrige al instante; tu progreso se guarda automáticamente en este navegador.
¿Cuál es la diferencia central entre WHERE y HAVING?
Una vista (VIEW) guarda una copia de los datos en el momento en que se crea, igual que una tabla común.
¿Cuál de las siguientes consultas es válida en PostgreSQL?
Tenés una vista resumen_ventas_2026 que filtra WHERE fecha >= '2026-01-01'. Insertás una fila nueva en ventas con fecha 2026-06-01. ¿Qué pasa la próxima vez que consultás la vista?
¿Por qué SELECT categoria, monto FROM ventas GROUP BY categoria; da error en PostgreSQL?
Cheat Sheet
| Forma | Para qué sirve |
|---|---|
GROUP BY columna | Agrupa filas que comparten el mismo valor en esa columna. |
COUNT() / SUM() / AVG() / MAX() / MIN() | Funciones de agregación: calculan un valor por grupo (o por toda la tabla, sin GROUP BY). |
WHERE | Filtra filas individuales, antes de agrupar. |
HAVING | Filtra grupos ya calculados, después de agrupar — admite funciones de agregación. |
CREATE VIEW nombre AS SELECT ... | Guarda una consulta con nombre; no copia datos. |
SELECT * FROM vista | Se consulta como una tabla común. |
CREATE OR REPLACE VIEW ... | Reemplaza la definición de una vista sin borrarla. |
¿Sabías que...?
🧮 No es un invento moderno
GROUP BY no lo inventó ninguna versión reciente de SQL: el estándar SQL-92 (1992) ya lo incluía junto con HAVING, pensado exactamente para separar "filtrar filas" de "filtrar resultados ya agregados".
🔒 Las vistas también sirven para seguridad
Una vista puede exponer solo algunas columnas o filas de una tabla sensible, y se pueden dar permisos (GRANT) sobre la vista sin dar acceso directo a la tabla completa — un adelanto de lo que vas a ver en la guía de roles y GRANT/REVOKE.
💾 Existen las vistas materializadas
A diferencia de una vista común, una vista materializada (MATERIALIZED VIEW) sí guarda una copia física del resultado, que hay que refrescar a mano — útil cuando la consulta original es cara de recalcular cada vez.