El día de hoy veremos cómo extraer datos de 2 tablas SQL distintas, combinarlos y extraer solo los primeros de ellos a partir de un parámetro de ordenamiento.
El ejemplo que mostraré va a mostrar los productos más vendidos en una tienda, pero esos productos se venden al contado o en apartado, y cada uno está en una tabla distinta.
Lo que se requiere es obtener los productos más vendidos sin importar a cuál tabla pertenecen, ordenándolos, combinándolos y extrayendo solo los primeros N.
No sé si existe una consulta SQL para combinar y ordenar datos de distintas tablas. No se me ocurre, así que mejor he usado el lenguaje de programación. El algoritmo queda así:
Veamos cómo hacerlo con un ejemplo práctico con Golang.
Nota: el “motor” de base de datos que uso es SQLite3.
Primero veamos cómo obtener los datos por separados según cada tabla:
func (d *DatosGraficasController) productosMasVendidosAlContado(fechaInicio, fechaFin string) []ProductoVendidoParaGrafica {
db, err := d.AjustesUsuario.obtenerBaseDeDatos()
if err != nil {
panic(err)
}
defer db.Close()
filas, err := db.Query(`SELECT SUM(productos_vendidos.cantidadVendida) AS vecesVendido,
productos_vendidos.idProducto,
productos_vendidos.codigoBarras,
productos_vendidos.descripcion
FROM productos_vendidos
INNER JOIN ventas_contado
ON productos_vendidos.idVenta = ventas_contado.idVenta
AND ventas_contado.fecha
BETWEEN ?
AND ?
GROUP BY productos_vendidos.idProducto,codigoBarras, descripcion
ORDER BY vecesVendido DESC LIMIT ?;`, fechaInicio, fechaFin, LimiteProductosMasVendidos)
if err != nil {
log.Printf("Error al consultar productos más vendidos:\n%q", err)
}
defer filas.Close()
productos := []ProductoVendidoParaGrafica{}
for filas.Next() {
var producto ProductoVendidoParaGrafica
err = filas.Scan(&producto.VecesVendido, &producto.IdProducto, &producto.CodigoBarras, &producto.Descripcion)
if err != nil {
log.Printf("Error al escanear producto más vendido:\n%q", err)
}
productos = append(productos, producto)
}
return productos
}
func (d *DatosGraficasController) productosMasVendidosEnApartados(fechaInicio, fechaFin string) []ProductoVendidoParaGrafica {
db, err := d.AjustesUsuario.obtenerBaseDeDatos()
if err != nil {
panic(err)
}
defer db.Close()
filas, err := db.Query(`SELECT SUM(productos_apartados.cantidadVendida) AS vecesVendido,
productos_apartados.idProducto,
productos_apartados.codigoBarras,
productos_apartados.descripcion
FROM productos_apartados
INNER JOIN apartados
ON productos_apartados.idApartado = apartados.idApartado
AND apartados.fecha
BETWEEN ?
AND ?
AND apartados.abonado + apartados.anticipo >= apartados.monto
GROUP BY productos_apartados.idProducto,productos_apartados.codigoBarras, productos_apartados.descripcion
ORDER BY vecesVendido DESC LIMIT ?;`, fechaInicio, fechaFin, LimiteProductosMasVendidos)
if err != nil {
log.Printf("Error al consultar productos más vendidos:\n%q", err)
}
defer filas.Close()
productos := []ProductoVendidoParaGrafica{}
for filas.Next() {
var producto ProductoVendidoParaGrafica
err = filas.Scan(&producto.VecesVendido, &producto.IdProducto, &producto.CodigoBarras, &producto.Descripcion)
if err != nil {
log.Printf("Error al escanear producto más vendido:\n%q", err)
}
productos = append(productos, producto)
}
return productos
}
Fíjate que una tabla es productos_vendidos
y otra tabla es productos_apartados
. La consulta es prácticamente la misma, solo que de distintas tablas. Ambas son ordenadas por la cantidad vendida.
Los obtenemos a continuación y los combinamos, luego los ordenamos con quicksort y finalmente los cortamos en caso de que superen la cantidad deseada:
func (d *DatosGraficasController) productosMasVendidos(fechaInicio, fechaFin string) []ProductoVendidoParaGrafica {
// Nota: "masVendidosContado" se usa para mezclar tanto los vendidos al contado como apartados, se usa para evitar declarar un temporal
masVendidosApartados := d.productosMasVendidosEnApartados(fechaInicio, fechaFin)
masVendidosContado := d.productosMasVendidosAlContado(fechaInicio, fechaFin)
for _, producto := range masVendidosApartados {
indice := existeProducto(masVendidosContado, producto)
if indice != -1 {
masVendidosContado[indice].VecesVendido += producto.VecesVendido
} else {
masVendidosContado = append(masVendidosContado, producto)
}
}
quicksort(&masVendidosContado, 0, int64(len(masVendidosContado)-1))
if len(masVendidosContado) > LimiteProductosMasVendidos {
return masVendidosContado[:LimiteProductosMasVendidos]
} else {
return masVendidosContado
}
}
Por cierto, la función existeProducto
debería llamarse indiceProducto
y devuelve el índice de un producto en un arreglo o -1
si no existe:
func existeProducto(productos []ProductoVendidoParaGrafica, productoBuscado ProductoVendidoParaGrafica) int {
for indice, producto := range productos {
if producto.IdProducto == productoBuscado.IdProducto {
return indice
}
}
return -1
}
Y finalmente aquí tenemos la función quicksort que ordena ese arreglo de tipo Struct:
func particion(arreglo *[]ProductoVendidoParaGrafica, izquierda, derecha int64) int64 {
var pivote = (*arreglo)[izquierda]
for {
for (*arreglo)[izquierda].VecesVendido > pivote.VecesVendido {
izquierda++
}
for (*arreglo)[derecha].VecesVendido < pivote.VecesVendido {
derecha--
}
if izquierda >= derecha {
return derecha
} else {
(*arreglo)[izquierda], (*arreglo)[derecha] = (*arreglo)[derecha], (*arreglo)[izquierda]
izquierda++
derecha--
}
}
}
func quicksort(arreglo *[]ProductoVendidoParaGrafica, izquierda, derecha int64) {
if izquierda < derecha {
indiceParticion := particion(arreglo, izquierda, derecha)
quicksort(arreglo, izquierda, indiceParticion)
quicksort(arreglo, indiceParticion+1, derecha)
}
}
Por cierto, estoy usando apuntadores en Go aunque más tarde descubrí que no son necesarios y que podemos modificar un arreglo a partir de su índice cuando se pasa a una función… pero ya no quise probar y modificar, además, si funciona no lo toques.
Si alguien conoce alguna manera de hacerlo con puro SQL agradecería si coloca su solución en los comentarios.
El día de hoy te mostraré cómo crear un servidor HTTP (servidor web) en Android…
En este post te voy a enseñar a designar una carpeta para imprimir todos los…
En este artículo te voy a enseñar la guía para imprimir en una impresora térmica…
Hoy te voy a mostrar un ejemplo de programación para agregar un módulo de tasa…
Los usuarios del plugin para impresoras térmicas pueden contratar licencias, y en ocasiones me han…
Hoy voy a enseñarte cómo imprimir el € en una impresora térmica. Vamos a ver…
Esta web usa cookies.