En este post te voy a enseñar a guardar solo algunos días de la semana elegidos por algún usuario usando MySQL usando un número entero, de modo que el usuario puede elegir todos los días de la semana, ninguno de ellos, solo sábado y domingo, solo lunes, solo el martes o cualquier combinación de días.
Vamos a usar un número entero y máscaras de bits para almacenar los días de la semana elegidos por el usuario, además de enseñarte a hacer consultas SQL para saber si cierto día de la semana se encuentra en los días que el usuario ha elegido.
Gracias a las máscaras de bits podemos almacenar una combinación de días de la semana con MySQL. Al final, MySQL solo guardará un entero cuyo valor máximo será 127.
Antes de revisar la conversión de los días a un entero, recordemos que un byte tiene 8 bits, y que una semana tiene 7 días. Así que un byte alcanza perfectamente para guardar los 7 días, guardando 1 día por bit. Con este método incluso sobra un bit.
Posicionalmente, de derecha a izquierda (suponiendo que el MSB está al inicio), los bits de un byte valen 128, 64, 32, 16, 8, 4, 2 y 1. Entonces podemos usar el bit que vale 1 (que a su vez es el LSB) para el domingo, 2 para lunes, 3 para martes y seguir así hasta llegar al sábado en el bit que vale 64.
En este caso el MSB que vale 128 no será usado.
Veamos algunos ejemplos:
00000001
, mismo que es 1
en base 1000000011
, mismo que es 3
01000100
, mismo que es 68
00101001
, que es 41
en decimal01111111
, es decir, 127
00000000
, que equivale a 0
en decimalObviamente hay muchas combinaciones, solo te estoy mostrando cómo podemos guardar cuáles días han sido elegidos a partir de un simple byte, usando 7 bits, un bit por día, guardando todas las combinaciones posibles.
Para guardar la combinación de días de la semana primero necesitamos calcular una máscara de bits según los días elegidos por el usuario. Aquí interviene el lenguaje de programación, ya que MySQL solo guardará el entero, nosotros debemos convertir el arreglo de días a una máscara de bits.
Yo lo he hecho con Go y queda así:
func diasAMascaraDeBits(diasDeLaSemana []string) int {
var mapaDeDiasConSuByteCorrespondiente = map[string]int{
"domingo": 1 << 0, // 1
"lunes": 1 << 1, // 2
"martes": 1 << 2, // 4
"miércoles": 1 << 3, // 8
"jueves": 1 << 4, // 16
"viernes": 1 << 5, // 32
"sábado": 1 << 6, // 64
}
mascaraDeBits := 0
for _, dia := range diasDeLaSemana {
if byteCorrespondienteAlDiaDeLaSemana, byteExiste := mapaDeDiasConSuByteCorrespondiente[dia]; byteExiste {
mascaraDeBits |= byteCorrespondienteAlDiaDeLaSemana
}
}
return mascaraDeBits
}
El proceso ocurre en la línea que tiene mascaraDeBits |= byteCorrespondienteAlDiaDeLaSemana
, ahí se está haciendo una operación de tipo OR que sería equivalente a: mascaraDeBits = mascaraDeBits | byteCorrespondienteAlDiaDeLaSemana
Recordemos que la operación OR va a devolver true
si al menos uno de los dos elementos es true
, pero en este caso solo se está encendiendo el bit correspondiente y que es el único bit encendido en el byte correspondiente al día de la semana.
¿Cómo sabemos cuál posición establecer? cada byte correspondiente al día de la semana en el mapa ya tiene un 1 establecido en la posición dependiendo del día.
Pasemos a convertir la máscara de bits, que será un día entero, a una lista de días. En este proceso también usamos un mapa, pero ahora comprobamos si cada día (representado por un bit) está presente en la máscara, y solo así agregamos el nombre del día a la lista.
Para este caso el mapa está inverso, pues la clave es el byte y el valor es el día.
func mascaraDeBitsAListaDeDias(enteroRepresentandoMascaraDeBits int) string {
// Mapa inverso de valores en bits a días de la semana
var mapaDeBytesConSuDiaCorrespondiente = map[int]string{
1 << 0: "Domingo", // 1
1 << 1: "Lunes", // 2
1 << 2: "Martes", // 4
1 << 3: "Miércoles", // 8
1 << 4: "Jueves", // 16
1 << 5: "Viernes", // 32
1 << 6: "Sábado", // 64
}
var dias []string
for byteDelDiaDeLaSemana, nombreDelDia := range mapaDeBytesConSuDiaCorrespondiente {
if enteroRepresentandoMascaraDeBits&byteDelDiaDeLaSemana == byteDelDiaDeLaSemana {
dias = append(dias, nombreDelDia)
}
}
var diasComoCadena string
for indiceDia, dia := range dias {
diasComoCadena += dia
if indiceDia+1 < len(dias) {
diasComoCadena += ", "
}
}
return diasComoCadena
}
De nuevo, el proceso ocurre en la siguiente línea:
if enteroRepresentandoMascaraDeBits&byteDelDiaDeLaSemana == byteDelDiaDeLaSemana
Para comprobar si un bit está encendido en cierta posición, podemos hacer una operación de tipo AND que va a devolver true
solo si ambos bits en la misma posición están encendidos.
El resultado de la operación debe ser exactamente igual al byte del día de la semana que estamos comprobando, ya que el byte de entrada (byteDelDiaDeLaSemana
) solo tiene un bit encendido, los demás están apagados.
Nota: es importante que comprendas la operación enteroRepresentandoMascaraDeBits&byteDelDiaDeLaSemana == byteDelDiaDeLaSemana
ya que la usaremos de nuevo al consultar con MySQL.
A partir del día de la semana (según los mapas definidos previamente) veamos cómo hacer una consulta SQL filtrando solo aquellos datos donde la máscara de bits contenga el día de la semana.
Vamos a suponer que queremos filtrar los datos donde los días elegidos contengan el día domingo, mismo que es representado como 1
. La consulta queda así:
SELECT * FROM tabla WHERE ((tabla.mascaraDeBits & 1) = 1)
Suponiendo que necesitamos aquellas filas donde la combinación de los días de la semana contenga el día sábado, que corresponde al número 64, la consulta quedaría así:
SELECT * FROM tabla WHERE ((tabla.mascaraDeBits & 64) = 64)
Y podemos hacer esto con cualquier otro día de la semana.
Existen más maneras de guardar cualquier combinación de días de la semana en MySQL.
Yo quise hacerlo con máscaras de bits porque me parece interesante y optimizado, pero también podrías guardar la lista de días como una cadena separada con comas y luego usar INSTR
o funciones similares para saber si, dado un nombre de día de la semana, el mismo está presente en la cadena de combinación de días.
Hoy te voy a presentar un creador de credenciales que acabo de programar y que…
Ya te enseñé cómo convertir una aplicación web de Vue 3 en una PWA. Al…
En este artículo voy a documentar la arquitectura que yo utilizo al trabajar con WebAssembly…
En un artículo anterior te enseñé a crear un PWA. Al final, cualquier aplicación que…
Al usar Comlink para trabajar con los workers usando JavaScript me han aparecido algunos errores…
En este artículo te voy a enseñar cómo usar un "top level await" esperando a…
Esta web usa cookies.