Agregación · SQL analítico

Porcentaje del total en SQL con SUM() OVER()

Calcula qué parte representa cada categoría, estado o grupo respecto al total sin perder una fila por grupo.

Lectura: 8–10 minIntermediaEjemplo comprobado
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

Porcentaje sobre el total
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.

SQLite · ejecutado sobre la base educativa
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;
Resultado comprobado
estadoimporteporcentaje_total
enviado1818.7046.25
cancelado1272.9032.37
pendiente840.3021.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

  1. Resume primero la unidad que quieres comparar. La CTE por_estado produce una fila por estado.
  2. Calcula el denominador sin agrupar de nuevo. SUM(importe) OVER () ve las tres filas de la CTE y obtiene 3931,90 para cada una.
  3. Divide la parte por el total. Cada importe se divide por ese denominador común y se multiplica por 100.
  4. 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: SUM está entre las funciones de agregación que pueden utilizarse como funciones de ventana.
  • SQL Server: la documentación de OVER incluye 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 * 100

Multiplicar 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

Intermedia · agregación + ventana

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.

Qué debes recordar

  • El porcentaje del total es parte ÷ total × 100.
  • GROUP BY puede crear las partes y SUM(...) OVER () calcular el total sin perder esas filas.
  • Usa un tipo decimal o 100.0 cuando la división entera pueda truncar decimales.
  • NULLIF(total, 0) evita dividir por cero cuando el denominador puede ser cero.
  • PARTITION BY cambia la pregunta: permite calcular porcentajes dentro de cada partición.
  • Los porcentajes redondeados fila a fila no siempre suman exactamente 100,00.

Conceptos relacionados

Fuentes técnicas consultadas