sqlpostgresqlmysqlclickhouse

MOD y el operador % en SQL: el resto de la division en la practica

Como MOD y el operador % calculan el resto: par/impar, cada N-esima fila, sharding por id y la regla del signo con negativos.

3 min de lecturaReferencesql · postgresql · mysql · clickhouse · math · sharding

MOD(a, b) devuelve el resto de dividir a entre b, el mismo valor que el operador %. Es la herramienta basica para comprobar la paridad, elegir cada N-esima fila, repartir filas en shards de forma uniforme por un identificador y cualquier logica ciclica.

Comportamiento basico

MOD(a, b) y a % b producen el mismo resultado: el resto que queda tras la division entera. El signo del resultado importa, y volveremos a ello mas abajo.

SELECT
  MOD(10, 3)  AS a,   -- 1
  10 % 3      AS b,   -- 1
  MOD(9, 3)   AS c,   -- 0
  MOD(7, 10)  AS d;   -- 7

Puntos clave:

  • MOD(a, b) y a % b son intercambiables en PostgreSQL y MySQL; usa el que se lea mas claro.
  • Si a se divide exactamente entre b, el resto es 0. Esa es tu prueba de divisibilidad.
  • Un divisor 0 es un error: MOD(5, 0) lanza division by zero, igual que la division normal.

Par/impar y cada N-esima fila

El resto entre 2 separa al instante las filas en pares e impares. Etiqueta los pedidos por la paridad de su id:

SELECT
  id,
  amount,
  CASE WHEN MOD(id, 2) = 0 THEN 'even' ELSE 'odd' END AS parity
FROM orders
ORDER BY id;

El mismo truco escala a "cada N-esima fila". Para tomar aproximadamente uno de cada diez usuarios como muestra de control, filtra por el resto:

SELECT id, email, country
FROM users
WHERE MOD(id, 10) = 0
ORDER BY id;

Trampa. MOD(id, 10) = 0 solo funciona como muestreo si los valores de id son densos y estan espaciados de forma uniforme. Sobre un id secuencial es fiable, pero sobre UUIDs o secuencias con grandes huecos la muestra se sesga. Para un muestreo justo sobre texto, toma el resto de un hash, no del id en bruto.

Sharding por id % bucket

El resto es la forma natural de repartir filas en un numero fijo de buckets. Distribuye los usuarios en 4 shards segun su id:

SELECT
  MOD(id, 4) AS shard,
  COUNT(*)   AS users
FROM users
GROUP BY MOD(id, 4)
ORDER BY shard;

Cada usuario cae siempre en el mismo shard 0..3, lo que resulta util para procesar por lotes o desplegar funciones por cohortes:

SELECT id, email
FROM users
WHERE MOD(id, 4) = 2   -- process only shard 2 in this batch
ORDER BY id;

Esto funciona porque, para id no negativos, el resultado de MOD(id, 4) siempre cae en 0..3. Los valores negativos se comportan distinto, lo vemos a continuacion.

La regla del signo y los negativos

La trampa principal de MOD: el signo del resultado sigue el signo del dividendo (el primer argumento), no del divisor. Es el comportamiento del estandar SQL en PostgreSQL y MySQL.

SELECT
  MOD(-10, 3)  AS a,   -- -1
  MOD(10, -3)  AS b,   -- 1
  MOD(-10, -3) AS c;   -- -1

Por eso, MOD(value, n) = 0 para paridad y divisibilidad es seguro, pero MOD(value, n) como indice de bucket no lo es: para un value negativo da un bucket negativo. Para obtener siempre un resto no negativo, suma el divisor y vuelve a tomar el resto:

-- always lands in 0..(n-1), even for negative input
SELECT MOD(MOD(-10, 3) + 3, 3) AS bucket;  -- 2

Recuerda tambien NULL: si cualquiera de los argumentos es NULL, el resultado es NULL, no 0. Tenlo en cuenta en WHERE y GROUP BY.

Diferencias entre bases de datos

  • PostgreSQL y MySQL: ambos admiten MOD(a, b) y a % b con la misma regla de signo (por el dividendo).
  • PostgreSQL: % funciona con enteros y numeric. Para tipos decimales existe MOD(numeric, numeric), por ejemplo MOD(5.5, 2) es 1.5.
  • ClickHouse: usa modulo(a, b) o el operador %; tambien existe moduloOrZero(a, b), que devuelve 0 en vez de fallar con un divisor cero, y positiveModulo(a, b) para un resultado siempre no negativo.
-- ClickHouse: safe modulo and always-positive modulo
SELECT
  moduloOrZero(10, 0)     AS a,   -- 0, no error
  positiveModulo(-10, 3)  AS b;   -- 2

Practica con ejercicios reales

Resuelve ejercicios en el entrenador de SQL con corrección instantánea y pistas.

Abrir el entrenador