sql

Combinar y ordenar 2 tablas de base de datos

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.

Explicación del algoritmo

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í:

  1. Obtener los datos de la tabla 1, ordenados de mayor a menor
  2. Obtener los datos de la tabla 2, ordenados de mayor a menor
  3. Combinar los datos de la tabla 1 con la 2. Recorremos los datos de la tabla 2, si el dato ya existe en los datos de la tabla 1, aumentamos su cantidad (cantidad vendida en este caso) y si no lo agregamos a los datos de la tabla 1.
  4. Ordenamos los datos por la cantidad vendida
  5. Limitamos los datos a la cantidad deseada. Por ejemplo, solo obtenemos los primeros 10 y descartamos los demás.

Veamos cómo hacerlo con un ejemplo práctico con Golang.

Nota: el “motor” de base de datos que uso es SQLite3.

Ejemplo con Go

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.

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.