bases de datos

Total vendido agrupado por mes – SQL y reportes con gráficas

En este post vamos a ver cómo calcular el total vendido en un período de fechas agrupado por mes, ya sea para mostrar un reporte o una gráfica de barras o líneas.

Básicamente veremos el algoritmo y ejemplos de código para sacar el desglose del total vendido por mes en el año o en cualquier período de tiempo, tomando los datos de una base de datos, agrupando, ordenando, sumando, etcétera.

No importa cuál motor de base de datos utilices ni el lenguaje de programación. Veremos el algoritmo para sumar el total vendido por mes.

Explicación del algoritmo

Necesitamos consultar la suma del total o monto agrupado por mes ordenado por mes. Para esto necesitamos haber guardado antes:

  1. El total o monto de la venta
  2. La fecha completa o el mes

Yo recomiendo guardar la fecha completa y luego obtener el mes, así podemos consultar esto por períodos, por ejemplo, el total vendido en un año pero agrupado por mes.

La consulta queda así:

select mes, sum(monto) from tabla group by mes;

Ahora veamos algunos ejemplos y cómo extraer el total de mes o presentarlo en el cliente.

Ejemplo con MySQL

Vamos a crear una tabla muy simple donde solo guardamos fecha y monto de la venta. En este caso no registramos la hora, solo la fecha. Creamos y llenamos la tabla:

CREATE TABLE ventas(
    fecha varchar(255) NOT NULL,
    monto decimal(9, 2) NOT NULL
);

INSERT INTO
    ventas(fecha, monto)
VALUES
    ("2023-02-12", 25.9),
    ("2023-03-12", 11.52),
    ("2023-03-12", 69.5),
    ("2023-02-14", 524.22),
    ("2023-01-12", 77.7),
    ("2023-03-12", 25),
    ("2023-04-12", 875),
    ("2023-03-09", 32),
    ("2023-05-14", 857),
    ("2023-05-12", 5);

Para obtener la suma de lo que se ha vendido en el año pero agrupado por mes la consulta quedaría así:

SELECT
    sum(monto) AS total,
    MONTH(fecha) AS mes
FROM
    ventas
WHERE
    fecha >= "2023-01-01"
    AND fecha <= "2023-12-31"
GROUP BY
    mes
ORDER BY
    mes;

Con la siguiente salida mostrando el número de mes y la sumatoria de los montos, o el total recaudado:

total mes
77.70 1
550.12 2
138.02 3
875.00 4
862.00 5

Obviamente tú puedes ordenar por mes o total, además de obtener también el año. Una vez que tienes el número de mes ya puedes obtener el nombre del mismo o hacer cualquier otra cosa.

Ese ejemplo que te mostré fue con MySQL, y a partir de aquí puedes usar cualquier lenguaje de programación para ejecutar esa consulta. Sigue leyendo para ver cómo mostrar una gráfica con esos datos.

Ejemplo de total vendido agrupado por mes con SQLite 3

Veamos un ejemplo con SQLite3. Yo he dividido los datos, por un lado tengo órdenes (es decir, órdenes o pedidos de un sistema de restaurantes) y por el otro tengo los pagos.

Primero veamos las órdenes que es en donde se guarda la fecha:

id,id_usuario,id_mesa,fecha_solicitud
23,1,1,2023-02-25T12:37:07
24,1,1,2023-02-25T12:41:45
25,1,1,2023-02-26T09:48:45
26,1,1,2023-02-26T09:55:13
27,1,1,2023-02-26T10:09:12
28,1,1,2023-02-26T14:58:28
29,1,1,2023-01-26T15:05:04
30,1,1,2023-02-23T12:06:09
31,1,1,2023-02-02T12:24:08
32,1,1,2023-02-02T12:25:52
33,1,2,2023-01-30T12:36:50
34,1,1,2023-04-22T15:24:18
35,1,1,2023-01-30T21:01:00
36,1,1,2023-02-27T11:43:53
37,1,1,2023-01-31T18:47:06
38,1,2,2023-02-01T10:00:30
39,1,2,2023-02-01T10:01:27
40,1,2,2023-02-01T10:59:01
41,1,2,2023-02-01T11:00:55
42,1,1,2023-02-01T11:26:28
43,1,2,2023-02-01T11:36:43
44,1,1,2023-02-02T08:55:21
45,1,1,2023-02-02T09:30:38
46,1,1,2023-02-02T10:05:20
47,1,1,2023-02-02T10:05:36
48,1,1,2023-02-02T10:06:02
49,1,1,2023-02-02T17:52:08
50,1,2,2023-02-02T20:42:09
51,1,2,2023-02-02T21:27:46
52,1,2,2023-02-02T21:36:06
53,2,2,2023-02-02T21:39:00
54,2,1,2023-02-03T11:04:23
55,2,1,2023-02-03T11:40:04
56,2,2,2023-02-03T11:40:15

Ahora veamos los pagos:

id,id_orden,id_metodo_pago,monto
1,23,4,40.0
2,24,4,270.0
3,25,4,15.0
4,26,4,20.0
5,27,4,15.0
6,28,4,24.0
7,29,4,20.0
8,30,4,15.0
9,31,4,15.0
10,33,4,20.0
11,32,4,20.0
12,34,4,20.0
13,35,4,255.0
14,36,4,1254.0
15,38,4,20.0
16,39,5,15.0
17,40,4,15.0
18,41,4,40.0
19,37,4,15.0
20,43,4,20.0
21,42,4,2140.0
22,44,4,20.0
23,45,4,1500.0
24,46,6,2000.0
25,47,4,2015.0
26,48,5,15000.0
27,50,4,30.0
28,51,4,7500.0
29,52,4,300000.0
30,53,4,15.0
31,49,4,20.0
32,54,4,20.0
33,55,4,20.0
34,56,4,20.0

Los campos importantes aquí son monto de la tabla pagos, y fecha_solicitud de la tabla ordenes.

Como ya tengo la fecha, debo calcular el mes y eso se puede hacer en SQLite con strftime usando el formato %m entonces mi consulta queda así:

SELECT
    strftime("%m", ordenes.fecha_solicitud) AS mes,
    coalesce(sum(pagos.monto), 0) AS total_vendido
FROM
    pagos
    INNER JOIN ordenes ON ordenes.id = pagos.id_orden
    AND ordenes.fecha_solicitud >= "2023-01-01T00:00:00"
    AND ordenes.fecha_solicitud <= "2023-12-31T23:59:59"
GROUP BY
    mes
ORDER BY
    mes

Por cierto, aquí uso coalesce en caso de que no haya ventas en ese mes, aunque sería poco probable porque si no hay ventas el mes ni siquiera aparecería; de igual manera lo dejo por buenas prácticas.

Estoy haciendo un inner join porque son dos tablas. Ya te mostré anteriormente un ejemplo más simple.

Nota importante: en este caso estoy filtrando las fechas para que solo sean del año 2023. Si tú usas otro formato de fechas tienes que cambiar el rango. Yo uso ese formato porque me permite comparar las cadenas lexicográficamente y luego crear objetos de tipo Date con JavaScript sin problemas.

Eso me dará una salida como la siguiente:

mes,total_vendido
01,310.0
02,332098.0
04,20.0

Ya tengo el número de mes y el total vendido en ese mes o mejor dicho; el mes y la suma de los totales de ese mes. Con esto ya podemos mostrar reportes y gráficas.

Bonus: mostrando datos en gráfica

Te voy a enseñar un ejemplo específico que yo uso para mostrar el total vendido en un año agrupado por mes usando datos de SQL y chart.js.

Yo uso Golang en el servidor. Mi función queda así:

func obtenerTotalVendidoEnPeriodoAgrupadoPorMetodoDePago(fechaInicio, fechaFin string) ([]EtiquetaConTotal, error) {
 diasConTotalVendido := []EtiquetaConTotal{}
 bd, err := obtenerBD()
 if err != nil {
  return diasConTotalVendido, err
 }

 defer bd.Close()
 filas, err := bd.Query(`SELECT
    strftime("%m", ordenes.fecha_solicitud) AS mes,
    coalesce(sum(pagos.monto), 0) AS total_vendido
FROM
    pagos
    INNER JOIN ordenes ON ordenes.id = pagos.id_orden
    AND ordenes.fecha_solicitud >= ?
    AND ordenes.fecha_solicitud <= ?
GROUP BY
    mes
ORDER BY
    mes`, fechaInicio, fechaFin)
 if err != nil {
  return diasConTotalVendido, err
 }
 var diaConTotalVendido EtiquetaConTotal
 for filas.Next() {
  err = filas.Scan(&diaConTotalVendido.Etiqueta, &diaConTotalVendido.Total)
  if err != nil {
   return diasConTotalVendido, err
  }
  diasConTotalVendido = append(diasConTotalVendido, diaConTotalVendido)
 }
 return diasConTotalVendido, nil
}

Luego expongo eso en una API creada con gorilla/mux y la consumo con JavaScript:

 enrutador.HandleFunc("/total_vendido_agrupado_por_mes", func(w http.ResponseWriter, r *http.Request) {
  responderHttpConFuncion(w, r, func() (interface{}, error) {
   valores, err := validarParametrosGetDePeticionHttp(r, []string{"fechaInicio", "fechaFin"})
   if err != nil {
    return nil, err
   }
   fechaInicio, fechaFin := valores[0], valores[1]
   return obtenerTotalVendidoEnPeriodoAgrupadoPorMes(fechaInicio, fechaFin)
  })
 }).Methods(http.MethodGet).Name("Escritorio: ver total vendido en período de fechas agrupado por mes")

Luego la obtengo en el lado del cliente y hago la gráfica convirtiendo el número de mes para que sea el label, y el total para que sea la data:

this.grafica = new Chart(document.querySelector("#graficaMes"), {
  type: "line",
  data: {
    labels: diasConTotal.map((dato) => {
      const fecha = new Date(fechaInicioMes);
      fecha.setDate(dato.etiqueta);
      return formateadorMes(fecha);
    }),
    datasets: [
      {
        label: "Recaudado",
        data: diasConTotal.map((dato) => dato.total),
        backgroundColor: ConstantesService.BACKGROUND_COLORS,
        borderColor: ConstantesService.BORDER_COLORS,
        borderWidth: 1,
      },
    ],
  },
  options: GraficasService.obtenerOpciones(),
});

Con el siguiente resultado:

Ventas totales por mes en un año usando SQL

Espero que con estos ejemplos puedas crear tus propios reportes y gráficas.

Solo es cuestión de encontrar la consulta adecuada según tu motor de base de datos y luego mostrar esos datos de la manera que prefieras, ya sea en una tabla, en una gráfica de barras, circular o lo que tú prefieras.

Estoy aquí para ayudarte 🤝💻


Estoy aquí para ayudarte en todo lo que necesites. Si requieres alguna modificación en lo presentado en este post, deseas asistencia con tu tarea, proyecto o precisas desarrollar un software a medida, no dudes en contactarme. Estoy comprometido a brindarte el apoyo necesario para que logres tus objetivos. Mi correo es parzibyte(arroba)gmail.com, estoy como@parzibyte en Telegram o en mi página de contacto

No te pierdas ninguno de mis posts 🚀🔔

Suscríbete a mi canal de Telegram para recibir una notificación cuando escriba un nuevo tutorial de programación.
parzibyte

Programador freelancer listo para trabajar contigo. Aplicaciones web, móviles y de escritorio. PHP, Java, Go, Python, JavaScript, Kotlin y más :) https://parzibyte.me/blog/software-creado-por-parzibyte/

Entradas recientes

Creador de credenciales web – Aplicación gratuita

Hoy te voy a presentar un creador de credenciales que acabo de programar y que…

1 semana hace

Desplegar PWA creada con Vue 3, Vite y SQLite3 en Apache

Ya te enseñé cómo convertir una aplicación web de Vue 3 en una PWA. Al…

2 semanas hace

Arquitectura para wasm con Go, Vue 3, Pinia y Vite

En este artículo voy a documentar la arquitectura que yo utilizo al trabajar con WebAssembly…

2 semanas hace

Vue 3 y Vite: crear PWA (Progressive Web App)

En un artículo anterior te enseñé a crear un PWA. Al final, cualquier aplicación que…

2 semanas hace

Errores de Comlink y algunas soluciones

Al usar Comlink para trabajar con los workers usando JavaScript me han aparecido algunos errores…

2 semanas hace

Esperar promesa para inicializar Store de Pinia con Vue 3

En este artículo te voy a enseñar cómo usar un "top level await" esperando a…

2 semanas hace

Esta web usa cookies.