Tento článok je momentálne v ruštine — anglický preklad sa pripravuje.
CEIL (он же CEILING) округляет число вверх до ближайшего целого, которое не меньше исходного значения. Это рабочая лошадка для подсчёта страниц пагинации, разбиения данных по корзинам и любой логики «сколько контейнеров нужно, чтобы вместить N штук».
Особенность функции в том, что она кодирует не математическую красоту, а инженерное ограничение: остаток нельзя разрезать пополам и положить на ту же страницу, в тот же батч или в ту же коробку.
Базовое поведение
CEIL всегда движется в сторону плюс бесконечности. Целое число остаётся как есть; любая дробная часть тянет результат вверх.
SELECT
CEIL(4.1) AS a,
CEIL(4.0) AS b,
CEIL(-4.1) AS c,
CEILING(7.0001) AS d;
Ключевые моменты:
CEIL и CEILING полностью идентичны во всех основных СУБД — это синонимы.
- Для отрицательных чисел «вверх» означает в сторону нуля:
CEIL(-4.1) равно -4, а не -5.
- В PostgreSQL результат над
numeric остаётся типом numeric, а над double precision — double precision. Сама дробная часть всегда обнуляется.
Подсчёт страниц пагинации
Классический случай: есть total строк и per_page на страницу — сколько всего страниц? Целочисленное деление обрежет «хвост», поэтому нужен CEIL.
SELECT
COUNT(*) AS total_orders,
CEIL(COUNT(*)::numeric / 25) AS total_pages
FROM orders
WHERE status = 'paid';
Тот же приём для каждой страны — сколько экранов писем нужно, чтобы показать всех пользователей по 20 на страницу:
SELECT
country,
COUNT(*) AS users,
CEIL(COUNT(*)::numeric / 20) AS pages
FROM users
GROUP BY country
ORDER BY users DESC;
Подводный камень — целочисленное деление. В PostgreSQL 5 / 2 равно 2 (integer), и CEIL(5 / 2) вернёт 2, а не 3. Деление уже произошло до вызова CEIL. Всегда приводите хотя бы один операнд к дробному типу: CEIL(5::numeric / 2) или CEIL(5.0 / 2). Эта ошибка особенно коварна, потому что на «ровных» данных (когда total кратен per_page) результат совпадает с правильным, и баг всплывает только на последней неполной странице.
Бакетинг и разбиение по диапазонам
CEIL отлично раскладывает непрерывные значения по дискретным корзинам фиксированной ширины. Разложим сотрудников по зарплатным «полкам» шириной 10000:
SELECT
CEIL(salary / 10000.0) * 10000 AS salary_band,
COUNT(*) AS people
FROM employees
GROUP BY CEIL(salary / 10000.0)
ORDER BY salary_band;
Сотрудник с зарплатой 41000 попадёт в полку 50000 (верхняя граница диапазона 40001–50000). Если нужна нижняя граница, используйте FLOOR. Аналогично можно резать суммы заказов на ценовые сегменты:
SELECT
CEIL(amount / 100.0) AS price_bucket,
COUNT(*) AS orders
FROM orders
GROUP BY CEIL(amount / 100.0)
ORDER BY price_bucket;
CEIL против FLOOR и ROUND
Три функции, три разных направления:
CEIL(x) — вверх, к плюс бесконечности: CEIL(2.1) = 3, CEIL(2.9) = 3.
FLOOR(x) — вниз, к минус бесконечности: FLOOR(2.9) = 2, FLOOR(-2.1) = -3.
ROUND(x) — к ближайшему целому: ROUND(2.4) = 2, ROUND(2.5) = 3.
SELECT
x,
CEIL(x) AS up,
FLOOR(x) AS down,
ROUND(x) AS nearest
FROM (VALUES (2.1), (2.5), (2.9), (-2.5)) AS t(x);
Для пагинации и «сколько контейнеров нужно» берите только CEIL: даже одна лишняя строка требует ещё одну целую страницу, а ROUND ошибётся ровно на половине.
Различия между СУБД
- PostgreSQL и MySQL:
CEIL и CEILING работают одинаково; в MySQL тоже остерегайтесь целочисленного деления — используйте / с дробными литералами или функцию.
- ClickHouse: функция называется
ceil и принимает второй аргумент — точность: ceil(4.123, 1) равно 4.2. Это удобно для округления вверх до десятых или сотых, чего нет в стандартном CEIL.
- Во всех системах
CEIL(NULL) возвращает NULL — учитывайте это в агрегатах и COALESCE.
SELECT ceil(4.123, 1) AS r;
Итог: CEIL выбирают не потому, что он «округляет красиво», а потому что даже малый остаток требует ещё одну целую единицу: страницу, коробку, батч или верхнюю границу диапазона. Проверьте только тип деления — если оно целочисленное, правильная идея даст неправильный ответ.
CEIL(он жеCEILING) округляет число вверх до ближайшего целого, которое не меньше исходного значения. Это рабочая лошадка для подсчёта страниц пагинации, разбиения данных по корзинам и любой логики «сколько контейнеров нужно, чтобы вместить N штук».Особенность функции в том, что она кодирует не математическую красоту, а инженерное ограничение: остаток нельзя разрезать пополам и положить на ту же страницу, в тот же батч или в ту же коробку.
Базовое поведение
CEILвсегда движется в сторону плюс бесконечности. Целое число остаётся как есть; любая дробная часть тянет результат вверх.SELECT CEIL(4.1) AS a, -- 5 CEIL(4.0) AS b, -- 4 CEIL(-4.1) AS c, -- -4 (toward zero, not away) CEILING(7.0001) AS d; -- 8Ключевые моменты:
CEILиCEILINGполностью идентичны во всех основных СУБД — это синонимы.CEIL(-4.1)равно-4, а не-5.numericостаётся типомnumeric, а надdouble precision—double precision. Сама дробная часть всегда обнуляется.Подсчёт страниц пагинации
Классический случай: есть
totalстрок иper_pageна страницу — сколько всего страниц? Целочисленное деление обрежет «хвост», поэтому нуженCEIL.SELECT COUNT(*) AS total_orders, CEIL(COUNT(*)::numeric / 25) AS total_pages FROM orders WHERE status = 'paid';Тот же приём для каждой страны — сколько экранов писем нужно, чтобы показать всех пользователей по 20 на страницу:
SELECT country, COUNT(*) AS users, CEIL(COUNT(*)::numeric / 20) AS pages FROM users GROUP BY country ORDER BY users DESC;Подводный камень — целочисленное деление. В PostgreSQL
5 / 2равно2(integer), иCEIL(5 / 2)вернёт2, а не3. Деление уже произошло до вызоваCEIL. Всегда приводите хотя бы один операнд к дробному типу:CEIL(5::numeric / 2)илиCEIL(5.0 / 2). Эта ошибка особенно коварна, потому что на «ровных» данных (когдаtotalкратенper_page) результат совпадает с правильным, и баг всплывает только на последней неполной странице.Бакетинг и разбиение по диапазонам
CEILотлично раскладывает непрерывные значения по дискретным корзинам фиксированной ширины. Разложим сотрудников по зарплатным «полкам» шириной 10000:SELECT CEIL(salary / 10000.0) * 10000 AS salary_band, COUNT(*) AS people FROM employees GROUP BY CEIL(salary / 10000.0) ORDER BY salary_band;Сотрудник с зарплатой 41000 попадёт в полку
50000(верхняя граница диапазона 40001–50000). Если нужна нижняя граница, используйтеFLOOR. Аналогично можно резать суммы заказов на ценовые сегменты:SELECT CEIL(amount / 100.0) AS price_bucket, COUNT(*) AS orders FROM orders GROUP BY CEIL(amount / 100.0) ORDER BY price_bucket;CEIL против FLOOR и ROUND
Три функции, три разных направления:
CEIL(x)— вверх, к плюс бесконечности:CEIL(2.1) = 3,CEIL(2.9) = 3.FLOOR(x)— вниз, к минус бесконечности:FLOOR(2.9) = 2,FLOOR(-2.1) = -3.ROUND(x)— к ближайшему целому:ROUND(2.4) = 2,ROUND(2.5) = 3.SELECT x, CEIL(x) AS up, FLOOR(x) AS down, ROUND(x) AS nearest FROM (VALUES (2.1), (2.5), (2.9), (-2.5)) AS t(x);Для пагинации и «сколько контейнеров нужно» берите только
CEIL: даже одна лишняя строка требует ещё одну целую страницу, аROUNDошибётся ровно на половине.Различия между СУБД
CEILиCEILINGработают одинаково; в MySQL тоже остерегайтесь целочисленного деления — используйте/с дробными литералами или функцию.ceilи принимает второй аргумент — точность:ceil(4.123, 1)равно4.2. Это удобно для округления вверх до десятых или сотых, чего нет в стандартномCEIL.CEIL(NULL)возвращаетNULL— учитывайте это в агрегатах иCOALESCE.-- ClickHouse: round up to one decimal place SELECT ceil(4.123, 1) AS r; -- 4.2Итог: CEIL выбирают не потому, что он «округляет красиво», а потому что даже малый остаток требует ещё одну целую единицу: страницу, коробку, батч или верхнюю границу диапазона. Проверьте только тип деления — если оно целочисленное, правильная идея даст неправильный ответ.