Tämä artikkeli on tällä hetkellä venäjäksi — englanninkielinen käännös on työn alla.
REPEAT(str, n) повторяет строку str ровно n раз подряд и возвращает результат как одно значение text. Это рабочий инструмент строкового форматирования: из него собираются разделители для отчётов, каркасы плейсхолдеров вида ?, ?, ? под динамические запросы и компактные текстовые бар-чарты прямо в выдаче SQL — без цикла на стороне приложения.
Главное преимущество REPEAT в том, что длина повтора задаётся числом, а не хардкодом: вместо строки из сорока дефисов вы пишете REPEAT('-', 40), а само число можно вычислить из данных — например из длины имени или из значения метрики. Повтор остаётся внутри запроса, поэтому ширина разделителя, размер бара и форма плейсхолдера всегда согласованы с тем, что реально вернул SELECT. Ниже разобраны четыре сценария, где это особенно удобно, и граничные случаи n <= 0 и NULL, на которых чаще всего спотыкаются.
Базовый синтаксис
Аргументов два: что повторять и сколько раз. Результат — обычный text.
SELECT REPEAT('ab', 3);
SELECT REPEAT('-', 20);
SELECT REPEAT(' ', 4) || 'indented';
Что стоит держать в голове:
- Повторяется любая строка, не только один символ:
REPEAT('=-', 5) даёт =-=-=-=-=-.
- Результат легко склеивать через
|| с другим текстом.
- Тип возвращаемого значения всегда строковый, даже если внутри цифры:
REPEAT('0', 3) — это '000', а не число 0.
В PostgreSQL и MySQL функция называется и работает одинаково — REPEAT(str, n). В ClickHouse есть repeat(s, n) с тем же смыслом.
Разделители и заголовки отчётов
Когда отчёт отдаётся в консоль или в текстовый дамп, ровные линейки делают его читаемым. REPEAT избавляет от хардкода длинных строк дефисов.
SELECT REPEAT('=', 40) AS header_rule
UNION ALL
SELECT ' MONTHLY REVENUE REPORT'
UNION ALL
SELECT REPEAT('=', 40);
Длину линейки можно привязать к данным — например, к длине самого длинного имени, чтобы подчёркивание точно совпадало по ширине:
SELECT
name,
REPEAT('-', LENGTH(name)) AS underline
FROM users
ORDER BY name
LIMIT 5;
Плейсхолдеры для динамических запросов
Иногда нужно собрать список ?, ?, ? или $1, $2, $3 под массив параметров переменной длины. REPEAT строит каркас, а лишний хвостовой разделитель убирается через TRIM или RTRIM.
SELECT RTRIM(REPEAT('?,', 3), ',');
Тот же приём годится для нумерованных плейсхолдеров PostgreSQL, если их генерирует код приложения, а вам нужно лишь проверить форму строки. Главное правило: повторяйте «элемент плюс разделитель», а потом отрезайте последний разделитель — так не придётся возиться с условием «не ставить запятую после последнего».
На практике число повторений почти всегда динамическое: его подставляют из длины массива параметров. Поэтому n стоит считать в коде или в подзапросе и передавать в REPEAT уже готовым целым, а не зашивать константу. Так одна и та же заготовка строки работает и для трёх значений, и для тридцати.
Текстовые бар-чарты и LPAD
Самый наглядный сценарий — мини-гистограмма прямо в результате. Берём числовую метрику, делим на масштаб и рисуем столько блоков, сколько получилось.
SELECT
country,
COUNT(*) AS users_cnt,
REPEAT('#', (COUNT(*) / 10)::int) AS bar
FROM users
GROUP BY country
ORDER BY users_cnt DESC;
Каждый символ # здесь стоит за 10 пользователей. Чтобы столбцы выровнялись в аккуратную колонку, добавьте LPAD — он дополняет строку пробелами слева до фиксированной ширины:
SELECT
dept,
ROUND(AVG(salary)) AS avg_salary,
LPAD(REPEAT('#', (AVG(salary) / 1000)::int), 30) AS bar
FROM employees
GROUP BY dept
ORDER BY avg_salary DESC;
REPEAT рисует столбец, а LPAD(..., 30) гарантирует, что все строки занимают ровно 30 знаков — диаграмма не «съезжает». Связка REPEAT + LPAD/RPAD — основной рабочий приём для ASCII-визуализаций.
Граничные случаи с n <= 0
Здесь прячется главная ловушка. Если n равно нулю или отрицательно, REPEAT возвращает пустую строку, а не NULL и не ошибку.
SELECT REPEAT('x', 0);
SELECT REPEAT('x', -5);
Из этого следуют важные выводы:
- В бар-чарте метрика, давшая
0 блоков, нарисует пустую строку — столбца просто не будет видно. Если нужен хотя бы маркер, подставьте минимум: REPEAT('#', GREATEST(value, 1)).
- Если
n оказывается NULL (например, из-за NULL в данных), весь результат REPEAT становится NULL. Подстрахуйтесь через COALESCE(n, 0).
- Дробное
n молча округляется/усекается под целое, поэтому считайте n заранее и приводите к int явно через ::int.
Опасность REPEAT почти всегда не в самой функции, а в том, откуда приходит n. На счастливом пути со статичной константой все три движка ведут себя одинаково, но как только n берётся из метрики, длины массива или результата COUNT, в него легко просачивается ноль, отрицательное число или NULL из данных. Поэтому стоит прогнать пограничные значения отдельно: REPEAT('#', 0), REPEAT('#', -1) и REPEAT('#', NULL) — чтобы увидеть, где появится пустая строка, а где весь столбец станет NULL, прежде чем это всплывёт в отчёте.
Ещё одна тонкость — размер результата. REPEAT('ab', 1000000) строит строку на два миллиона символов, и если n приходит из недоверенного ввода, такой повтор легко раздувает память и ширину строки в выдаче. Для текстовых баров ограничивайте множитель сверху (например, через LEAST), а саму формулу для n считайте и приводите к int заранее, а не зашивайте предположение прямо внутрь вызова REPEAT.
Ловушка: легко перепутать REPEAT с конкатенацией. REPEAT('ab', 3) — это 'ababab' (одна строка из шести символов), а не три отдельные строки. Если нужны именно строки таблицы, используйте generate_series, а REPEAT оставьте для форматирования текста.
REPEAT(str, n)повторяет строкуstrровноnраз подряд и возвращает результат как одно значениеtext. Это рабочий инструмент строкового форматирования: из него собираются разделители для отчётов, каркасы плейсхолдеров вида?, ?, ?под динамические запросы и компактные текстовые бар-чарты прямо в выдаче SQL — без цикла на стороне приложения.Главное преимущество
REPEATв том, что длина повтора задаётся числом, а не хардкодом: вместо строки из сорока дефисов вы пишетеREPEAT('-', 40), а само число можно вычислить из данных — например из длины имени или из значения метрики. Повтор остаётся внутри запроса, поэтому ширина разделителя, размер бара и форма плейсхолдера всегда согласованы с тем, что реально вернулSELECT. Ниже разобраны четыре сценария, где это особенно удобно, и граничные случаиn <= 0иNULL, на которых чаще всего спотыкаются.Базовый синтаксис
Аргументов два: что повторять и сколько раз. Результат — обычный
text.SELECT REPEAT('ab', 3); -- ababab SELECT REPEAT('-', 20); -- a 20-dash rule SELECT REPEAT(' ', 4) || 'indented';Что стоит держать в голове:
REPEAT('=-', 5)даёт=-=-=-=-=-.||с другим текстом.REPEAT('0', 3)— это'000', а не число0.В PostgreSQL и MySQL функция называется и работает одинаково —
REPEAT(str, n). В ClickHouse естьrepeat(s, n)с тем же смыслом.Разделители и заголовки отчётов
Когда отчёт отдаётся в консоль или в текстовый дамп, ровные линейки делают его читаемым.
REPEATизбавляет от хардкода длинных строк дефисов.SELECT REPEAT('=', 40) AS header_rule UNION ALL SELECT ' MONTHLY REVENUE REPORT' UNION ALL SELECT REPEAT('=', 40);Длину линейки можно привязать к данным — например, к длине самого длинного имени, чтобы подчёркивание точно совпадало по ширине:
SELECT name, REPEAT('-', LENGTH(name)) AS underline FROM users ORDER BY name LIMIT 5;Плейсхолдеры для динамических запросов
Иногда нужно собрать список
?, ?, ?или$1, $2, $3под массив параметров переменной длины.REPEATстроит каркас, а лишний хвостовой разделитель убирается черезTRIMилиRTRIM.-- Build "?,?,?" for an IN-list of 3 values SELECT RTRIM(REPEAT('?,', 3), ','); -- ?,?,?Тот же приём годится для нумерованных плейсхолдеров PostgreSQL, если их генерирует код приложения, а вам нужно лишь проверить форму строки. Главное правило: повторяйте «элемент плюс разделитель», а потом отрезайте последний разделитель — так не придётся возиться с условием «не ставить запятую после последнего».
На практике число повторений почти всегда динамическое: его подставляют из длины массива параметров. Поэтому
nстоит считать в коде или в подзапросе и передавать вREPEATуже готовым целым, а не зашивать константу. Так одна и та же заготовка строки работает и для трёх значений, и для тридцати.Текстовые бар-чарты и LPAD
Самый наглядный сценарий — мини-гистограмма прямо в результате. Берём числовую метрику, делим на масштаб и рисуем столько блоков, сколько получилось.
SELECT country, COUNT(*) AS users_cnt, REPEAT('#', (COUNT(*) / 10)::int) AS bar FROM users GROUP BY country ORDER BY users_cnt DESC;Каждый символ
#здесь стоит за 10 пользователей. Чтобы столбцы выровнялись в аккуратную колонку, добавьтеLPAD— он дополняет строку пробелами слева до фиксированной ширины:SELECT dept, ROUND(AVG(salary)) AS avg_salary, LPAD(REPEAT('#', (AVG(salary) / 1000)::int), 30) AS bar FROM employees GROUP BY dept ORDER BY avg_salary DESC;REPEATрисует столбец, аLPAD(..., 30)гарантирует, что все строки занимают ровно 30 знаков — диаграмма не «съезжает». СвязкаREPEAT+LPAD/RPAD— основной рабочий приём для ASCII-визуализаций.Граничные случаи с n <= 0
Здесь прячется главная ловушка. Если
nравно нулю или отрицательно,REPEATвозвращает пустую строку, а неNULLи не ошибку.SELECT REPEAT('x', 0); -- '' (empty string) SELECT REPEAT('x', -5); -- '' (empty string)Из этого следуют важные выводы:
0блоков, нарисует пустую строку — столбца просто не будет видно. Если нужен хотя бы маркер, подставьте минимум:REPEAT('#', GREATEST(value, 1)).nоказываетсяNULL(например, из-заNULLв данных), весь результатREPEATстановитсяNULL. Подстрахуйтесь черезCOALESCE(n, 0).nмолча округляется/усекается под целое, поэтому считайтеnзаранее и приводите кintявно через::int.Опасность
REPEATпочти всегда не в самой функции, а в том, откуда приходитn. На счастливом пути со статичной константой все три движка ведут себя одинаково, но как толькоnберётся из метрики, длины массива или результатаCOUNT, в него легко просачивается ноль, отрицательное число илиNULLиз данных. Поэтому стоит прогнать пограничные значения отдельно:REPEAT('#', 0),REPEAT('#', -1)иREPEAT('#', NULL)— чтобы увидеть, где появится пустая строка, а где весь столбец станетNULL, прежде чем это всплывёт в отчёте.Ещё одна тонкость — размер результата.
REPEAT('ab', 1000000)строит строку на два миллиона символов, и еслиnприходит из недоверенного ввода, такой повтор легко раздувает память и ширину строки в выдаче. Для текстовых баров ограничивайте множитель сверху (например, черезLEAST), а саму формулу дляnсчитайте и приводите кintзаранее, а не зашивайте предположение прямо внутрь вызоваREPEAT.Ловушка: легко перепутать
REPEATс конкатенацией.REPEAT('ab', 3)— это'ababab'(одна строка из шести символов), а не три отдельные строки. Если нужны именно строки таблицы, используйтеgenerate_series, аREPEATоставьте для форматирования текста.