BIT_AND / BIT_OR / BIT_XOR

MySQL incluye tres funciones de agregación que operan a nivel de bits: BIT_AND, BIT_OR y BIT_XOR. A diferencia de SUM o AVG que trabajan con valores numéricos como cantidades, estas funciones operan sobre la representación binaria de los números, combinando los bits individuales de cada valor del grupo. Aunque son menos conocidas que las funciones de agregación clásicas, resultan muy útiles cuando trabajas con campos de permisos, máscaras de bits, banderas de configuración o cualquier sistema que codifique información en bits individuales.

Sintaxis

BIT_AND(expresion)
BIT_OR(expresion)
BIT_XOR(expresion)

Las tres funciones aceptan una expresión numérica entera y devuelven un entero sin signo de 64 bits (BIGINT UNSIGNED). Todas ignoran los valores NULL.

Para entender qué hace cada una, veamos cómo operan a nivel de bits individuales:

BIT_AND devuelve un 1 en cada posición de bit solo si todos los valores del grupo tienen un 1 en esa posición. Es la operación AND lógica aplicada bit a bit. El resultado identifica los bits que están activos en todos los valores del grupo.

BIT_OR devuelve un 1 en cada posición de bit si al menos un valor del grupo tiene un 1 en esa posición. Es la operación OR lógica. El resultado identifica los bits que están activos en cualquiera de los valores del grupo.

BIT_XOR devuelve un 1 en cada posición de bit si un número impar de valores del grupo tienen un 1 en esa posición. Es la operación XOR (OR exclusivo).

Comportamiento básico

Veamos las tres funciones con un ejemplo simple usando valores pequeños para poder visualizar los bits:

SELECT
    BIT_AND(valor) AS resultado_and,
    BIT_OR(valor) AS resultado_or,
    BIT_XOR(valor) AS resultado_xor
FROM (
    SELECT 5 AS valor    -- binario: 101
    UNION ALL SELECT 7   -- binario: 111
    UNION ALL SELECT 5   -- binario: 101
) datos;
resultado_andresultado_orresultado_xor
577

Analicemos bit a bit:

  5 = 101
  7 = 111
  5 = 101
  -------
AND = 101 = 5  (solo las posiciones donde TODOS tienen 1)
 OR = 111 = 7  (las posiciones donde ALGUNO tiene 1)
XOR = 111 = 7  (posiciones con número impar de 1s)

Para el BIT_AND, solo la primera y tercera posición están activas en todos los valores, así que el resultado es 101 (5). Para BIT_OR, las tres posiciones tienen al menos un 1, así que el resultado es 111 (7). Para BIT_XOR, la primera posición tiene tres unos (impar), la segunda tiene uno (impar) y la tercera tiene tres (impar), así que el resultado es 111 (7).

Caso práctico: permisos con máscaras de bits

El caso de uso más común para estas funciones es trabajar con sistemas de permisos basados en bits. Imagina que cada permiso se representa con un bit diferente:

-- Definición de permisos:
-- 1  (001) = lectura
-- 2  (010) = escritura
-- 4  (100) = ejecución

Supongamos que tienes una tabla roles_usuario donde cada usuario tiene un campo permisos que almacena una máscara de bits:

SELECT
    usuario_id,
    permisos,
    BIN(permisos) AS permisos_binario,
    IF(permisos & 1, 'Sí', 'No') AS lectura,
    IF(permisos & 2, 'Sí', 'No') AS escritura,
    IF(permisos & 4, 'Sí', 'No') AS ejecucion
FROM roles_usuario
WHERE grupo = 'desarrollo';
usuario_idpermisospermisos_binariolecturaescrituraejecucion
17111
25101No
3311No
47111
511NoNo

Ahora podemos usar BIT_AND para encontrar los permisos comunes a todos los miembros del grupo:

SELECT
    BIT_AND(permisos) AS permisos_comunes,
    BIN(BIT_AND(permisos)) AS comunes_binario,
    IF(BIT_AND(permisos) & 1, 'Sí', 'No') AS todos_leen,
    IF(BIT_AND(permisos) & 2, 'Sí', 'No') AS todos_escriben,
    IF(BIT_AND(permisos) & 4, 'Sí', 'No') AS todos_ejecutan
FROM roles_usuario
WHERE grupo = 'desarrollo';
permisos_comunescomunes_binariotodos_leentodos_escribentodos_ejecutan
11NoNo

Solo el bit de lectura (1) está activo en todos los usuarios, así que BIT_AND devuelve 1. Esto significa que el permiso de lectura es el único que comparten todos los miembros del grupo.

Caso práctico: encontrar permisos presentes en cualquier miembro

BIT_OR encuentra los permisos que tiene al menos un miembro del grupo:

SELECT
    grupo,
    BIT_OR(permisos) AS cualquier_permiso,
    BIN(BIT_OR(permisos)) AS permisos_binario,
    IF(BIT_OR(permisos) & 1, 'Sí', 'No') AS alguien_lee,
    IF(BIT_OR(permisos) & 2, 'Sí', 'No') AS alguien_escribe,
    IF(BIT_OR(permisos) & 4, 'Sí', 'No') AS alguien_ejecuta
FROM roles_usuario
GROUP BY grupo;
grupocualquier_permisopermisos_binarioalguien_leealguien_escribealguien_ejecuta
desarrollo7111
marketing311No
soporte11NoNo

BIT_OR devuelve 7 (111) para desarrollo, lo que significa que entre todos los miembros se cubren los tres permisos. Marketing tiene lectura y escritura pero nadie ejecuta. Soporte solo tiene lectura.

Caso práctico: banderas de características de productos

Otro uso práctico es con banderas de características. Supongamos que cada producto tiene un campo caracteristicas donde cada bit representa una característica:

-- Definición de características:
-- 1   = envío gratuito
-- 2   = producto destacado
-- 4   = disponible online
-- 8   = disponible en tienda
-- 16  = oferta especial

Para ver qué características están presentes en cada categoría:

SELECT
    c.nombre AS categoria,
    BIT_OR(p.caracteristicas) AS todas_caracteristicas,
    IF(BIT_OR(p.caracteristicas) & 1, 'Sí', '-') AS envio_gratis,
    IF(BIT_OR(p.caracteristicas) & 2, 'Sí', '-') AS destacado,
    IF(BIT_OR(p.caracteristicas) & 4, 'Sí', '-') AS online,
    IF(BIT_OR(p.caracteristicas) & 8, 'Sí', '-') AS tienda,
    IF(BIT_OR(p.caracteristicas) & 16, 'Sí', '-') AS oferta
FROM productos p
JOIN categorias c ON p.categoria_id = c.id
GROUP BY c.nombre;
categoriatodas_caracteristicasenvio_gratisdestacadoonlinetiendaoferta
Smartphones31
Portátiles15-
Accesorios13--
Libros5---

La categoría Smartphones tiene todas las características (31 = 11111 en binario), mientras que Libros solo tiene envío gratuito y disponibilidad online.

Para encontrar las características comunes a todos los productos de cada categoría:

SELECT
    c.nombre AS categoria,
    BIT_AND(p.caracteristicas) AS caracteristicas_comunes,
    IF(BIT_AND(p.caracteristicas) & 4, 'Todos online', 'No todos') AS disponibilidad_online
FROM productos p
JOIN categorias c ON p.categoria_id = c.id
GROUP BY c.nombre;

Caso práctico: BIT_XOR para detectar cambios

BIT_XOR tiene un uso interesante para detectar si un valor cambió un número par o impar de veces, o para calcular checksums simples:

SELECT
    BIT_XOR(id) AS checksum_simple
FROM productos;
checksum_simple
23

Si eliminas o modificas un registro, el checksum cambiará. No es un mecanismo de seguridad robusto, pero sirve como verificación rápida de integridad.

Otra propiedad útil de XOR: si aplicas XOR dos veces con el mismo valor, vuelves al original. Esto significa que BIT_XOR puede detectar valores duplicados:

SELECT
    BIT_XOR(cantidad) AS xor_cantidades
FROM detalle_pedidos
WHERE pedido_id = 142;

Si todas las cantidades son iguales y hay un número par de líneas, el resultado será 0. Si hay un número impar, será el valor mismo de la cantidad.

Manejo de NULL

Las tres funciones ignoran los valores NULL. Si todas las filas tienen NULL, el comportamiento es específico: BIT_AND devuelve 18446744073709551615 (todos los bits a 1, que es el valor máximo de BIGINT UNSIGNED), BIT_OR devuelve 0 (todos los bits a 0), y BIT_XOR devuelve 0:

SELECT
    BIT_AND(NULL) AS and_null,
    BIT_OR(NULL) AS or_null,
    BIT_XOR(NULL) AS xor_null;
and_nullor_nullxor_null
1844674407370955161500

Estos son los valores de identidad para cada operación: AND con todos los bits a 1 no cambia el resultado al combinar con otros valores, y OR/XOR con 0 tampoco.

Combinación con otras funciones

Puedes combinar las funciones de bits con GROUP BY y HAVING para consultas sofisticadas. Por ejemplo, encontrar los grupos donde todos los miembros tienen al menos el permiso de lectura:

SELECT grupo
FROM roles_usuario
GROUP BY grupo
HAVING BIT_AND(permisos) & 1 = 1;

O encontrar los grupos donde ningún miembro tiene permiso de ejecución:

SELECT grupo
FROM roles_usuario
GROUP BY grupo
HAVING BIT_OR(permisos) & 4 = 0;

La primera consulta usa BIT_AND para verificar que todos tienen un bit activo. La segunda usa BIT_OR para verificar que ninguno tiene un bit activo. Este patrón de combinar la agregación de bits con operaciones de enmascaramiento (&) es la base de los sistemas de permisos basados en bits.

En la siguiente sección veremos las funciones de ventana, que permiten realizar cálculos sobre conjuntos de filas relacionadas.

Escrito por Eduardo Lázaro