Запрос с предложением LIMIT n BY expressions выбирает первые n строк для каждого уникального значения expressions. Ключ для LIMIT BY может содержать любое количество expressions.
ClickHouse поддерживает следующие варианты синтаксиса:
LIMIT [offset_value, ]n BY expressions
LIMIT n OFFSET offset_value BY expressions
Во время обработки запроса ClickHouse выбирает данные, упорядоченные по ключу сортировки. Ключ сортировки задается явно с помощью предложения ORDER BY или неявно как свойство движка таблицы (порядок строк гарантируется только при использовании ORDER BY; в противном случае из-за многопоточности блоки строк не будут упорядочены). Затем ClickHouse применяет LIMIT n BY expressions и возвращает первые n строк для каждой уникальной комбинации expressions. Если указан OFFSET, то для каждого блока данных, относящегося к отдельной комбинации expressions, ClickHouse пропускает offset_value строк с начала блока и возвращает в результате не более n строк. Если offset_value превышает число строк в блоке данных, ClickHouse не возвращает из этого блока ни одной строки.
LIMIT BY не связан с LIMIT. Оба можно использовать в одном и том же запросе.
Если вы хотите использовать номера столбцов вместо имен столбцов в предложении LIMIT BY, включите настройку enable_positional_arguments.
Пример таблицы:
CREATE TABLE limit_by(id Int, val Int) ENGINE = Memory;
INSERT INTO limit_by VALUES (1, 10), (1, 11), (1, 12), (2, 20), (2, 21);
Запросы:
SELECT * FROM limit_by ORDER BY id, val LIMIT 2 BY id;
┌─id─┬─val─┐
│ 1 │ 10 │
│ 1 │ 11 │
│ 2 │ 20 │
│ 2 │ 21 │
└────┴─────┘
SELECT * FROM limit_by ORDER BY id, val LIMIT 1, 2 BY id;
┌─id─┬─val─┐
│ 1 │ 11 │
│ 1 │ 12 │
│ 2 │ 21 │
└────┴─────┘
Запрос SELECT * FROM limit_by ORDER BY id, val LIMIT 2 OFFSET 1 BY id возвращает тот же результат.
Следующий запрос возвращает 5 лучших источников перехода для каждой пары domain, device_type при общем максимуме в 100 строк (LIMIT n BY + LIMIT).
SELECT
domainWithoutWWW(URL) AS domain,
domainWithoutWWW(REFERRER_URL) AS referrer,
device_type,
count() cnt
FROM hits
GROUP BY domain, referrer, device_type
ORDER BY cnt DESC
LIMIT 5 BY domain, device_type
LIMIT 100;
LIMIT BY также поддерживает отрицательные лимиты и смещения. Как и в случае с отрицательной командой LIMIT, с LIMIT BY можно использовать отрицательные значения, чтобы выбирать строки с конца каждой группы.
SELECT * FROM limit_by ORDER BY id, val LIMIT -2 BY id;
┌─id─┬─val─┐
│ 1 │ 11 │
│ 1 │ 12 │
│ 2 │ 20 │
│ 2 │ 21 │
└────┴─────┘
Возвращает последние 2 строки для каждого id. Для id = 1 получаем строки 11 и 12; для id = 2 возвращаются обе строки, так как в группе всего 2 строки.
SELECT * FROM limit_by ORDER BY id, val LIMIT -1 OFFSET -1 BY id;
┌─id─┬─val─┐
│ 1 │ 11 │
│ 2 │ 20 │
└────┴─────┘
Возвращает предпоследнюю строку для каждого id: завершающий OFFSET -1 отбрасывает последнюю строку в каждой группе, а ведущий -1 затем оставляет последнюю строку из оставшихся.
Также можно сочетать LIMIT и OFFSET с разными знаками. Например, чтобы отбросить первую строку каждой группы, а затем оставить последние 2 из оставшихся:
SELECT * FROM limit_by ORDER BY id, val LIMIT -2 OFFSET 1 BY id;
┌─id─┬─val─┐
│ 1 │ 11 │
│ 1 │ 12 │
│ 2 │ 21 │
└────┴─────┘
Для id = 1 первая строка (10) пропускается; возвращаются обе последние строки — 11 и 12. Для id = 2 первая строка (20) пропускается, остаётся только 21.
LIMIT BY ALL эквивалентен перечислению всех выражений в SELECT, которые не являются агрегатными функциями.
Например:
SELECT col1, col2, col3 FROM table LIMIT 2 BY ALL;
то же, что и
SELECT col1, col2, col3 FROM table LIMIT 2 BY col1, col2, col3;
В особом случае, если среди аргументов функции есть как агрегатные функции, так и другие поля, ключи LIMIT BY будут включать максимально возможное количество неагрегатных полей, которые можно из неё извлечь.
Например:
SELECT substring(a, 4, 2), substring(substring(a, 1, 2), 1, count(b)) FROM t LIMIT 2 BY ALL;
то же, что и
SELECT substring(a, 4, 2), substring(substring(a, 1, 2), 1, count(b)) FROM t LIMIT 2 BY substring(a, 4, 2), substring(a, 1, 2);
Пример таблицы:
CREATE TABLE limit_by(id Int, val Int) ENGINE = Memory;
INSERT INTO limit_by VALUES (1, 10), (1, 11), (1, 12), (2, 20), (2, 21);
Запросы:
SELECT * FROM limit_by ORDER BY id, val LIMIT 2 BY id;
┌─id─┬─val─┐
│ 1 │ 10 │
│ 1 │ 11 │
│ 2 │ 20 │
│ 2 │ 21 │
└────┴─────┘
SELECT * FROM limit_by ORDER BY id, val LIMIT 1, 2 BY id;
┌─id─┬─val─┐
│ 1 │ 11 │
│ 1 │ 12 │
│ 2 │ 21 │
└────┴─────┘
Запрос SELECT * FROM limit_by ORDER BY id, val LIMIT 2 OFFSET 1 BY id возвращает тот же результат.
С использованием LIMIT BY ALL:
SELECT id, val FROM limit_by ORDER BY id, val LIMIT 2 BY ALL;
Это эквивалентно:
SELECT id, val FROM limit_by ORDER BY id, val LIMIT 2 BY id, val;
Последнее изменение 10 июня 2026 г.