Contenido de esta guía
La idea: dividir cada parte por el total
Un porcentaje del total responde a una pregunta como “¿qué proporción de la facturación corresponde a cada estado del pedido?”. La fórmula es sencilla: parte ÷ total × 100. El reto en SQL es calcular el total sin perder las filas que quieres comparar.
Una función de ventana resuelve ese problema: SUM(importe) OVER () calcula el total sobre todas las filas del resultado y lo repite para cada una. Así puedes dividir cada importe por el mismo denominador sin colapsar el resultado en una sola fila.
Patrón general
WITH resumen AS (
SELECT grupo,
SUM(valor) AS importe
FROM tabla
GROUP BY grupo
)
SELECT grupo,
importe,
100.0 * importe / NULLIF(SUM(importe) OVER (), 0) AS porcentaje_total
FROM resumen;La CTE primero crea una fila por grupo. Después, SUM(importe) OVER () suma esos importes sin reagruparlos. 100.0 fuerza una operación decimal en motores o tipos donde dividir enteros podría truncar la parte fraccionaria. NULLIF(..., 0) evita usar cero como denominador y devuelve NULL cuando no existe un porcentaje matemáticamente definido.
Ejemplo comprobado con pedidos
En la base educativa, los pedidos tienen tres estados: enviado, cancelado y pendiente. Primero sumamos el importe por estado y después calculamos qué porcentaje representa cada subtotal respecto a la facturación completa.
WITH por_estado AS (
SELECT estado,
ROUND(SUM(total), 2) AS importe
FROM pedidos
GROUP BY estado
)
SELECT estado,
importe,
ROUND(
100.0 * importe / NULLIF(SUM(importe) OVER (), 0),
2
) AS porcentaje_total
FROM por_estado
ORDER BY porcentaje_total DESC;| estado | importe | porcentaje_total |
|---|---|---|
| enviado | 1818.70 | 46.25 |
| cancelado | 1272.90 | 32.37 |
| pendiente | 840.30 | 21.37 |
Los porcentajes mostrados suman 99,99 por el redondeo individual a dos decimales. Antes de redondear, cada fila se calcula contra el mismo total: 3931,90.
Cómo leer la consulta paso a paso
- Resume primero la unidad que quieres comparar. La CTE
por_estadoproduce una fila por estado. - Calcula el denominador sin agrupar de nuevo.
SUM(importe) OVER ()ve las tres filas de la CTE y obtiene 3931,90 para cada una. - Divide la parte por el total. Cada
importese divide por ese denominador común y se multiplica por 100. - Redondea al final. Esto evita que un redondeo prematuro cambie innecesariamente el cálculo.
Modelo mental: GROUP BY crea las partes; SUM(...) OVER () vuelve a mirar el conjunto de partes sin hacerlas desaparecer.
Por qué OVER () cambia el resultado
Sin OVER, SUM(importe) sería una agregación ordinaria y necesitarías otra agrupación o una subconsulta para combinarla con cada fila. Con OVER (), la suma se convierte en una agregación de ventana: calcula sobre el conjunto visible, pero conserva una salida por cada fila.
Si añades PARTITION BY, el denominador deja de ser global. Por ejemplo, SUM(importe) OVER (PARTITION BY mes) permitiría calcular el porcentaje de cada categoría dentro de su mes. La pregunta de negocio debe decidir cuál es el denominador correcto.
Compatibilidad entre motores
El patrón de agregaciones como funciones de ventana está disponible en PostgreSQL, MySQL 8.x, SQL Server y SQLite actuales. La sintaxis SUM(...) OVER () mantiene la misma idea en los cuatro motores.
- PostgreSQL: una agregación ordinaria puede actuar como función de ventana cuando lleva una cláusula
OVER. - MySQL 8.x:
SUMestá entre las funciones de agregación que pueden utilizarse como funciones de ventana. - SQL Server: la documentación de
OVERincluye explícitamente cálculos porcentuales con una suma de ventana. - SQLite: sus agregaciones incorporadas pueden utilizarse como funciones de ventana al añadir
OVER.
Los tipos numéricos y las reglas de división pueden variar. Si los operandos son enteros, usa un literal decimal como 100.0 o un CAST apropiado para evitar perder decimales.
Errores frecuentes
Usar el número de filas como denominador
COUNT(*) responde “cuántas filas hay”. Si buscas porcentaje de facturación, el denominador debe ser la suma de importes, no la cantidad de grupos.
Dividir enteros y perder decimales
-- Puede truncar según el motor y los tipos
importe / total * 100Multiplicar por 100.0 antes de dividir o convertir explícitamente a un tipo decimal hace visible que esperas un resultado fraccionario.
Confundir porcentaje global con porcentaje por grupo mayor
OVER () usa todas las filas visibles como una sola partición. Si necesitas “porcentaje dentro de cada mes”, “dentro de cada país” o “dentro de cada pedido”, debes definir ese contexto con PARTITION BY.
Esperar que los porcentajes redondeados sumen exactamente 100
Redondear cada fila por separado puede producir 99,99 o 100,01. No es necesariamente un error del cálculo: es una consecuencia del redondeo de cada parte.
Comprueba lo aprendido
Calcula el peso de cada estado
Usa la tabla pedidos para devolver estado, importe y porcentaje_total. Agrupa primero por estado y calcula después el porcentaje de cada subtotal respecto al importe total de todos los pedidos.
Criterio de éxito: mantienes una fila por estado, usas el mismo total como denominador y evitas división entera.
GROUP BY estado.SUM(importe) OVER () calcula el denominador sin eliminar filas.100.0 y protege el denominador con NULLIF(..., 0).Solución razonada
WITH por_estado AS (
SELECT estado,
SUM(total) AS importe
FROM pedidos
GROUP BY estado
)
SELECT estado,
importe,
ROUND(
100.0 * importe / NULLIF(SUM(importe) OVER (), 0),
2
) AS porcentaje_total
FROM por_estado
ORDER BY porcentaje_total DESC;La primera etapa define las partes. La función de ventana calcula el total de esas partes para cada fila, de modo que todas comparten el mismo denominador.
Qué debes recordar
- El porcentaje del total es parte ÷ total × 100.
GROUP BYpuede crear las partes ySUM(...) OVER ()calcular el total sin perder esas filas.- Usa un tipo decimal o
100.0cuando la división entera pueda truncar decimales. NULLIF(total, 0)evita dividir por cero cuando el denominador puede ser cero.PARTITION BYcambia la pregunta: permite calcular porcentajes dentro de cada partición.- Los porcentajes redondeados fila a fila no siempre suman exactamente 100,00.