Tento článek je momentálně v ruštině — anglický překlad se připravuje.
MOD(a, b) возвращает остаток от деления a на b — то же самое, что оператор %. Это базовый инструмент для проверки чётности, выборки каждой N-й строки, равномерного шардинга по идентификатору и любой циклической логики.
Базовое поведение
MOD(a, b) и a % b дают одинаковый результат: остаток, который остаётся после целочисленного деления. Знак результата важен, и мы вернёмся к нему ниже.
SELECT
MOD(10, 3) AS a,
10 % 3 AS b,
MOD(9, 3) AS c,
MOD(7, 10) AS d;
Ключевые моменты:
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
ORDER BY id;
Это работает, потому что для неотрицательных id результат MOD(id, 4) всегда лежит в диапазоне 0..3. С отрицательными значениями всё иначе — об этом ниже.
Правило знака и отрицательные числа
Главная ловушка MOD: знак результата повторяет знак делимого (первого аргумента), а не делителя. Это поведение SQL-стандарта в PostgreSQL и MySQL.
SELECT
MOD(-10, 3) AS a,
MOD(10, -3) AS b,
MOD(-10, -3) AS c;
Из-за этого MOD(value, n) = 0 для чётности и делимости безопасно, а вот MOD(value, n) как индекс корзины — нет: для отрицательного value он даст отрицательную корзину. Чтобы всегда получать неотрицательный остаток, добавьте делитель и возьмите остаток ещё раз:
SELECT MOD(MOD(-10, 3) + 3, 3) AS bucket;
Помните и про 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) для всегда неотрицательного результата.
SELECT
moduloOrZero(10, 0) AS a,
positiveModulo(-10, 3) AS b;
Итог: MOD хорош для циклов и корзин, пока вы явно контролируете знак, делитель и распределение исходного ключа. Для чётности хватит простого остатка, для шардирования отрицательных или хэшированных значений лучше сразу нормализовать результат в диапазон корзин.
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.Различия между СУБД
MOD(a, b), иa % bс одинаковым правилом знака (по делимому).%работает с целыми иnumeric. Для дробных типов естьMOD(numeric, numeric), напримерMOD(5.5, 2)равно1.5.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 хорош для циклов и корзин, пока вы явно контролируете знак, делитель и распределение исходного ключа. Для чётности хватит простого остатка, для шардирования отрицательных или хэшированных значений лучше сразу нормализовать результат в диапазон корзин.