sqlpostgresqlmysqlclickhouse

MOD and the % Operator in SQL: Remainders in Practice

How MOD and the % operator compute remainders: even/odd, every N-th row, sharding by id, and the sign rule for negatives.

2 min di letturaReferencesql · postgresql · mysql · clickhouse · math · sharding
Questo articolo è attualmente in russo — la traduzione in inglese è in corso.

MOD(a, b) возвращает остаток от деления a на b — то же самое, что оператор %. Это базовый инструмент для проверки чётности, выборки каждой N-й строки, равномерного шардинга по идентификатору и любой циклической логики.

Базовое поведение

MOD(a, b) и a % b дают одинаковый результат: остаток, который остаётся после целочисленного деления. Знак результата важен, и мы вернёмся к нему ниже.

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

Ключевые моменты:

  • MOD(a, b) и a % b взаимозаменяемы в PostgreSQL и MySQL — берите тот, что читается яснее.
  • Если a делится на b нацело, остаток равен 0. Это и есть проверка делимости.
  • Делитель 0 — ошибка: MOD(5, 0) бросает division by zero, как и обычное деление.

Чёт/нечёт и каждая N-я строка

Остаток от деления на 2 мгновенно делит строки на чётные и нечётные. Пометим заказы по чётности id:

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

Тот же приём масштабируется до «каждой N-й строки». Чтобы взять примерно каждого десятого пользователя для выборочной проверки, фильтруйте по остатку:

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

Подводный камень. MOD(id, 10) = 0 работает как семплинг, только если id распределены равномерно и без больших дыр. На последовательных id это надёжно, но на UUID или сильно «дырявых» последовательностях выборка перекосится. Для честного семплинга по тексту берите остаток от хэша, а не от сырого id.

Шардинг по id % bucket

Остаток — это естественный способ разложить строки по фиксированному числу корзин. Распределим пользователей по 4 шардам по их id:

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

Каждый пользователь стабильно попадает в один и тот же шард 0..3, что удобно для пакетной обработки или раскатки фич по когортам:

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

Это работает, потому что для неотрицательных id результат MOD(id, 4) всегда лежит в диапазоне 0..3. С отрицательными значениями всё иначе — об этом ниже.

Правило знака и отрицательные числа

Главная ловушка MOD: знак результата повторяет знак делимого (первого аргумента), а не делителя. Это поведение SQL-стандарта в PostgreSQL и MySQL.

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

Из-за этого MOD(value, n) = 0 для чётности и делимости безопасно, а вот MOD(value, n) как индекс корзины — нет: для отрицательного value он даст отрицательную корзину. Чтобы всегда получать неотрицательный остаток, добавьте делитель и возьмите остаток ещё раз:

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

Помните и про NULL: если любой из аргументов NULL, результат NULL, а не 0. Учитывайте это в WHERE и GROUP BY.

Различия между СУБД

  • PostgreSQL и MySQL: поддерживают и MOD(a, b), и a % b с одинаковым правилом знака (по делимому).
  • PostgreSQL: % работает с целыми и numeric. Для дробных типов есть MOD(numeric, numeric), например MOD(5.5, 2) равно 1.5.
  • ClickHouse: используйте modulo(a, b) или оператор %; есть ещё moduloOrZero(a, b), который возвращает 0 вместо ошибки при нулевом делителе, и positiveModulo(a, b) для всегда неотрицательного результата.
-- ClickHouse: safe modulo and always-positive modulo
SELECT
  moduloOrZero(10, 0)     AS a,   -- 0, no error
  positiveModulo(-10, 3)  AS b;   -- 2

Итог: MOD хорош для циклов и корзин, пока вы явно контролируете знак, делитель и распределение исходного ключа. Для чётности хватит простого остатка, для шардирования отрицательных или хэшированных значений лучше сразу нормализовать результат в диапазон корзин.

Esercitati su esercizi reali

Risolvi esercizi nel trainer SQL con valutazione e suggerimenti istantanei.

Apri il trainer