Иногда в SQL нужно не искать данные и не считать сумму, а просто аккуратно собрать строку. Например, нарисовать разделитель из дефисов, добавить отступ, сделать маленькую текстовую диаграмму или собрать список плейсхолдеров для динамического запроса.
Для таких задач есть функция REPEAT.
Она делает ровно то, что написано в названии: повторяет строку нужное количество раз.
SELECT REPEAT('-', 10) AS line;
Результат:
----------
Мы попросили SQL повторить символ - десять раз. Получилась одна строка из десяти дефисов.
Функция простая, но полезная. Особенно когда количество повторов не хочется писать руками, а нужно вычислить его из данных: длины имени, количества пользователей, размера метрики или числа параметров.
Что делает REPEAT
REPEAT(str, n) повторяет строку str ровно n раз подряд и возвращает результат как одну строку.
SELECT REPEAT('ab', 3) AS result;
Результат:
ababab
Важно: результат — это одна строка, а не несколько строк таблицы.
То есть REPEAT('ab', 3) возвращает:
ababab
А не:
ab
ab
ab
Если вам нужно создать несколько строк в результате запроса, для этого используют другие инструменты, например generate_series в PostgreSQL. REPEAT нужен именно для повторения текста внутри одного значения.
Базовый синтаксис
У функции два аргумента:
REPEAT(string, count)
Первый аргумент — что повторяем.
Второй аргумент — сколько раз повторяем.
Примеры:
SELECT REPEAT('x', 5) AS result;
Результат:
xxxxx
SELECT REPEAT('SQL ', 3) AS result;
Результат:
SQL SQL SQL
SELECT REPEAT('=-', 4) AS result;
Результат:
=-=-=-=-
Повторять можно не только один символ, но и целую строку. Это удобно, когда нужен узор, отступ, разделитель или повторяющийся фрагмент текста.
REPEAT возвращает строку
Даже если вы повторяете цифру, результат всё равно будет строкой, а не числом.
SELECT REPEAT('0', 5) AS result;
Результат:
00000
Это не число 0. Это текстовая строка из пяти нулей.
Эта разница важна. Например, строку 00000 можно склеивать с другими строками:
SELECT 'USR-' || REPEAT('0', 3) || '42' AS user_code;
Результат:
USR-00042
Но для настоящего форматирования кодов чаще используют LPAD, потому что он не просто повторяет символ, а дополняет конкретное значение до нужной длины.
REPEAT и обычная склейка строк
REPEAT часто используют вместе с оператором ||, который склеивает строки в PostgreSQL.
Например, сделаем отступ перед текстом:
SELECT REPEAT(' ', 4) || 'Hello' AS result;
Результат:
Hello
Здесь REPEAT(' ', 4) создаёт четыре пробела, а потом мы приклеиваем к ним слово Hello.
Такой приём можно использовать для простого форматирования текстовых отчётов.
Разделители для отчётов
Самый понятный сценарий для REPEAT — сделать ровную линию-разделитель.
Например:
SELECT REPEAT('-', 30) AS line;
Результат:
------------------------------
Такой разделитель можно использовать в текстовом отчёте:
SELECT REPEAT('=', 40) AS report_line
UNION ALL
SELECT ' MONTHLY REVENUE REPORT'
UNION ALL
SELECT REPEAT('=', 40);
Результат:
========================================
MONTHLY REVENUE REPORT
========================================
Конечно, современные отчёты чаще строятся в BI-системах, Excel, HTML или интерфейсе приложения. Но для консольных отчётов, технических выгрузок, логов и отладочных запросов такие приёмы всё ещё бывают полезны.
Главное преимущество здесь в том, что вы не пишете руками строку из сорока символов. Вы явно говорите REPEAT('=', 40). Это читается проще и выглядит аккуратнее.
Разделитель по длине значения
Иногда хочется, чтобы длина линии зависела от данных.
Например, нужно вывести имя и под ним линию такой же длины:
SELECT
name,
REPEAT('-', LENGTH(name)) AS underline
FROM users
ORDER BY name;
Если в таблице есть такие имена:
name
---------
Alex
Maria
Konstantin
результат будет таким:
name | underline
-----------+-----------
Alex | ----
Maria | -----
Konstantin | ----------
Здесь LENGTH(name) считает длину имени, а REPEAT('-', LENGTH(name)) создаёт линию такой же длины.
Это хороший пример того, зачем вообще нужен REPEAT: количество повторов можно не хардкодить, а вычислять прямо из данных.
Отступы и простое форматирование
REPEAT удобно использовать для отступов.
Например, представим таблицу категорий, где есть уровень вложенности:
name | level
--------------+------
Products | 0
Electronics | 1
Phones | 2
Laptops | 2
Clothes | 1
Можно вывести категории с отступами:
SELECT
REPEAT(' ', level) || name AS category_tree
FROM categories
ORDER BY id;
Результат:
Products
Electronics
Phones
Laptops
Clothes
Здесь мы повторяем два пробела столько раз, сколько указано в колонке level.
Если level = 0, отступа нет.
Если level = 1, будет два пробела.
Если level = 2, будет четыре пробела.
Получается простой текстовый вид дерева прямо в SQL.
Плейсхолдеры для динамических запросов
Ещё один практический сценарий — собрать строку с плейсхолдерами.
Например, приложению нужно подготовить SQL-запрос с условием IN, где количество значений заранее неизвестно.
Для трёх значений нужен такой фрагмент:
?,?,?
Его можно собрать через REPEAT:
SELECT RTRIM(REPEAT('?,', 3), ',') AS placeholders;
Результат:
?,?,?
Как это работает: REPEAT('?,', 3) сначала создаёт строку:
?,?,?,
А потом RTRIM(..., ',') убирает лишнюю запятую справа.
В итоге остаётся:
?,?,?
Такой приём удобен, когда повторяется один и тот же элемент с разделителем: «плейсхолдер плюс запятая».
Почему мы сначала добавляем лишнюю запятую
На первый взгляд странно: зачем сначала делать лишнюю запятую, а потом её удалять?
Потому что так проще.
Если собирать строку вручную, постоянно появляется проблема последнего элемента:
?, ?, ?
После первых двух плейсхолдеров запятая нужна, а после последнего — нет.
В программировании это частая задача. Один из простых способов её решить: повторять фрагмент вместе с разделителем, а потом убрать последний разделитель.
SELECT RTRIM(REPEAT('?, ', 4), ', ') AS placeholders;
Результат:
?, ?, ?, ?
Здесь строка сначала получится такой:
?, ?, ?, ?,
А потом хвостовой разделитель будет убран.
Важное предупреждение про динамический SQL
REPEAT может помочь собрать форму строки, например ?,?,?.
Но он не должен использоваться для склейки пользовательских значений прямо в SQL.
Плохая идея:
SELECT 'SELECT * FROM users WHERE id IN (' || user_input || ')';
Если user_input пришёл от пользователя, в нём может оказаться что угодно. Это риск SQL-инъекции.
Правильный подход: значения должны передаваться как параметры, а плейсхолдеры должны только обозначать места для этих параметров.
То есть REPEAT может помочь собрать каркас:
?,?,?
Но сами значения должны безопасно подставляться через механизм параметров в приложении или драйвере базы данных.
Нумерованные плейсхолдеры в PostgreSQL
В PostgreSQL часто используются нумерованные плейсхолдеры:
$1, $2, $3
Для них REPEAT уже не так удобен, потому что каждый элемент отличается номером.
Если нужно собрать именно такую строку внутри PostgreSQL, лучше использовать generate_series и string_agg:
SELECT string_agg('$' || n, ', ' ORDER BY n) AS placeholders
FROM generate_series(1, 3) AS g(n);
Результат:
$1, $2, $3
Это важное отличие.
REPEAT хорош, когда повторяется одинаковый фрагмент:
?, ?, ?
А если каждый фрагмент отличается:
$1, $2, $3
лучше использовать генерацию строк и агрегацию.
Текстовые бар-чарты
Один из самых приятных примеров REPEAT — маленькие текстовые диаграммы прямо в результате SQL-запроса.
Допустим, у нас есть пользователи по странам. Мы хотим не только вывести количество, но и показать простой визуальный столбик.
SELECT
country,
COUNT(*) AS users_count,
REPEAT('#', (COUNT(*) / 10)::int) AS bar
FROM users
GROUP BY country
ORDER BY users_count DESC;
Пример результата:
country | users_count | bar
-----------+-------------+----------
Georgia | 52 | #####
Kazakhstan | 31 | ###
Armenia | 18 | #
Здесь один символ # означает примерно 10 пользователей.
Если пользователей 52, получится 5 символов.
Если 31 — 3 символа.
Если 18 — 1 символ.
Это не замена настоящей визуализации, но для быстрого анализа в консоли очень удобно.
Как выбрать масштаб для бара
В текстовой диаграмме важно правильно выбрать масштаб.
Если написать так:
SELECT REPEAT('#', COUNT(*)::int) FROM users GROUP BY country;
и в стране 5000 пользователей, SQL нарисует строку из 5000 символов. Это будет неудобно и некрасиво.
Поэтому метрику обычно делят:
SELECT REPEAT('#', (COUNT(*) / 10)::int) FROM users GROUP BY country;
или:
SELECT REPEAT('#', (COUNT(*) / 100)::int) FROM users GROUP BY country;
Масштаб зависит от данных.
Например:
- если значения маленькие, можно делать 1 символ = 1 единица;
- если значения десятки, можно делать 1 символ = 10 единиц;
- если значения тысячи, можно делать 1 символ = 100 или 1000 единиц.
Хорошая диаграмма должна помещаться на экране и помогать сравнивать значения, а не превращаться в бесконечную строку.
Ограничение максимальной длины бара
Чтобы случайно не получить слишком длинную строку, можно ограничить количество символов сверху через LEAST.
Например:
SELECT
country,
COUNT(*) AS users_count,
REPEAT('#', LEAST((COUNT(*) / 10)::int, 30)) AS bar
FROM users
GROUP BY country
ORDER BY users_count DESC;
Здесь бар не будет длиннее 30 символов.
Даже если пользователей очень много, строка останется аккуратной.
Запись LEAST((COUNT(*) / 10)::int, 30) означает: взять меньшее из двух значений — рассчитанную длину бара или 30.
Это полезная защита для отчётов, особенно если данные могут резко вырасти.
Минимальный видимый бар
Есть и обратная проблема.
Если значение маленькое, после деления может получиться 0.
Например, 18 / 100 при целочисленном делении даёт 0.
Тогда REPEAT('#', 0) вернёт пустую строку, и визуально покажется, будто значения вообще нет.
Если вам нужно показывать хотя бы один символ для ненулевых значений, можно использовать GREATEST.
Например:
SELECT
country,
COUNT(*) AS users_count,
REPEAT('#', GREATEST((COUNT(*) / 10)::int, 1)) AS bar
FROM users
GROUP BY country
ORDER BY users_count DESC;
Но тут есть нюанс: если значение действительно равно нулю, такой запрос всё равно нарисует один символ. Поэтому для точной логики лучше использовать CASE.
SELECT
country,
COUNT(*) AS users_count,
CASE
WHEN COUNT(*) = 0 THEN ''
ELSE REPEAT('#', GREATEST((COUNT(*) / 10)::int, 1))
END AS bar
FROM users
GROUP BY country
ORDER BY users_count DESC;
Так мы говорим:
- если значение нулевое — бара нет;
- если значение больше нуля — покажи хотя бы один символ.
REPEAT вместе с LPAD и RPAD
REPEAT часто используют вместе с LPAD и RPAD.
REPEAT рисует сам бар:
SELECT REPEAT('#', 5);
Результат:
#####
А LPAD или RPAD помогают выровнять его до нужной ширины.
Например, выведем среднюю зарплату по отделам:
SELECT
dept,
ROUND(AVG(salary)) AS avg_salary,
RPAD(REPEAT('#', (AVG(salary) / 1000)::int), 30, ' ') AS bar
FROM employees
GROUP BY dept
ORDER BY avg_salary DESC;
Результат может выглядеть так:
dept | avg_salary | bar
------------+------------+------------------------------
Engineering | 5200 | #####
Marketing | 3100 | ###
Support | 2400 | ##
Здесь REPEAT('#', (AVG(salary) / 1000)::int) создаёт бар, а RPAD(..., 30, ' ') дополняет его пробелами справа до 30 символов, чтобы все строки занимали одинаковую ширину.
Если хочется выровнять бар вправо, можно использовать 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 можно использовать для простых рейтингов.
Допустим, в таблице товаров есть рейтинг от 1 до 5:
product_name | rating
-------------+-------
Keyboard | 4
Mouse | 5
Monitor | 3
Можно вывести рейтинг звёздочками:
SELECT
product_name,
rating,
REPEAT('★', rating) AS stars
FROM products
ORDER BY rating DESC;
Результат:
product_name | rating | stars
-------------+--------+-------
Mouse | 5 | ★★★★★
Keyboard | 4 | ★★★★
Monitor | 3 | ★★★
Такой формат удобен для демонстрационных отчётов и учебных задач.
Если нужно всегда показывать шкалу из пяти символов, можно добавить пустые звёзды:
SELECT
product_name,
rating,
REPEAT('★', rating) || REPEAT('☆', 5 - rating) AS stars
FROM products
ORDER BY rating DESC;
Результат:
product_name | rating | stars
-------------+--------+-------
Mouse | 5 | ★★★★★
Keyboard | 4 | ★★★★☆
Monitor | 3 | ★★★☆☆
Вот здесь REPEAT уже помогает собрать не просто повтор, а маленький понятный визуальный элемент.
Что будет, если количество повторов равно нулю
Если количество повторов равно 0, функция вернёт пустую строку.
SELECT REPEAT('x', 0) AS result;
Результат:
Там не будет ни одного символа.
Это не NULL и не ошибка. Это просто пустая строка.
В текстовых диаграммах это важно: если значение после расчёта превратилось в 0, столбик исчезнет.
Например:
SELECT REPEAT('#', (5 / 10)::int) AS bar;
Результат будет пустым, потому что 5 / 10 при целочисленном расчёте может дать 0.
Поэтому для баров часто используют GREATEST, если нужно показать хотя бы один символ.
Что будет, если количество повторов отрицательное
В PostgreSQL и MySQL отрицательное количество повторов обычно даёт пустую строку.
SELECT REPEAT('x', -3) AS result;
Результат:
Ошибки нет, но и символов тоже нет.
Это может быть неожиданно.
Например, если вы считаете длину бара по формуле value - 10 и value оказался меньше 10, количество повторов станет отрицательным. В результате бар просто исчезнет.
Чтобы защититься, можно использовать GREATEST:
SELECT REPEAT('#', GREATEST(value - 10, 0)) AS bar
FROM metrics;
Так мы говорим: если расчёт дал отрицательное число, возьми 0.
Что будет с NULL
Если строка или количество повторов равны NULL, результат тоже будет NULL.
SELECT REPEAT(NULL, 3) AS result;
Результат:
NULL
SELECT REPEAT('x', NULL) AS result;
Результат:
NULL
Это логично: если SQL не знает, что повторять или сколько раз повторять, он не может собрать строку.
Если в данных может быть NULL, используйте COALESCE.
Например:
SELECT REPEAT('#', COALESCE(score, 0)) AS bar
FROM metrics;
Здесь COALESCE(score, 0) означает: если score равен NULL, возьми 0.
Тогда вместо NULL получится пустая строка.
Если нужно заменить NULL в самой строке:
SELECT REPEAT(COALESCE(label, ''), 3) AS repeated_label
FROM labels;
Дробное количество повторов
Количество повторов должно быть целым числом.
Но в реальных запросах вы часто получаете дробное значение после деления или среднего значения.
Например, выражение AVG(salary) / 1000 может быть дробным.
Чтобы передать это значение в REPEAT, лучше явно привести его к целому типу.
В PostgreSQL:
SELECT REPEAT('#', (AVG(salary) / 1000)::int) AS bar
FROM employees;
Так запрос читается понятнее: мы явно показываем, что количество символов должно быть целым.
Но помните: приведение к int может округлять значение. Поэтому для отчётов лучше заранее решить, какая логика вам нужна:
- округлять;
- отбрасывать дробную часть;
- брать минимум;
- брать максимум;
- ограничивать сверху.
Например:
SELECT REPEAT('#', LEAST(ROUND(AVG(salary) / 1000)::int, 30)) AS bar
FROM employees;
Здесь мы округляем значение, приводим его к int и ограничиваем бар 30 символами.
Не давайте REPEAT бесконтрольно раздувать строку
REPEAT может создать очень большую строку.
Например:
SELECT REPEAT('ab', 1000000);
Это строка на два миллиона символов.
В учебном примере это просто странно. А в реальном запросе такое может нагрузить память, сеть и клиент, который получает результат.
Особенно осторожно нужно быть, если количество повторов приходит из данных или пользовательского ввода.
Плохая идея:
SELECT REPEAT('#', user_provided_number) AS bar
FROM metrics;
Если user_provided_number = 10000000, результат будет огромным.
Лучше ограничивать длину:
SELECT REPEAT('#', LEAST(user_provided_number, 100)) AS bar
FROM metrics;
Для текстовых диаграмм обычно достаточно 30, 50 или 100 символов. Всё, что больше, уже плохо читается.
REPEAT в PostgreSQL
В PostgreSQL функция называется repeat.
Обычно её пишут в верхнем регистре в статьях и примерах:
SELECT REPEAT('ha', 3) AS result;
Результат:
hahaha
Но в PostgreSQL имена функций не чувствительны к регистру, поэтому эти варианты эквивалентны:
SELECT repeat('ha', 3);
SELECT REPEAT('ha', 3);
Для строковой склейки в PostgreSQL часто используют оператор ||:
SELECT REPEAT('-', 10) || ' report ' || REPEAT('-', 10) AS title;
Результат:
---------- report ----------
REPEAT в MySQL
В MySQL функция тоже называется REPEAT.
Синтаксис такой же:
SELECT REPEAT('ab', 3) AS result;
Результат:
ababab
Для склейки строк в MySQL обычно используют CONCAT, а не ||:
SELECT CONCAT(REPEAT('-', 10), ' report ', REPEAT('-', 10)) AS title;
Результат:
---------- report ----------
Это важное отличие для новичков: сама функция REPEAT похожа, но способы склеивания строк в разных СУБД могут отличаться.
REPEAT в ClickHouse
В ClickHouse тоже есть функция repeat.
Пример:
SELECT repeat('ab', 3) AS result;
Результат:
ababab
Для текстовых отчётов и простых баров идея та же:
SELECT repeat('#', 5) AS bar;
Результат:
#####
При переносе запросов между PostgreSQL, MySQL и ClickHouse стоит отдельно проверить граничные случаи: 0, отрицательные значения, NULL и слишком большие числа. На обычных примерах функция выглядит одинаково, а различия чаще появляются именно на краях.
REPEAT не заменяет LPAD и RPAD
Иногда новички пытаются использовать REPEAT там, где лучше подходит LPAD или RPAD.
Например, нужно превратить 42 в 000042.
Через REPEAT можно попытаться сделать так:
SELECT REPEAT('0', 4) || '42' AS code;
Результат:
000042
Но это работает только потому, что мы заранее знаем: нужно ровно четыре нуля.
Если значение будет другим, например 7 или 12345, количество нулей уже нужно считать отдельно.
Для таких задач лучше использовать LPAD:
SELECT LPAD('42', 6, '0') AS code;
Результат:
000042
LPAD сам понимает, сколько символов нужно добавить слева до нужной длины.
Запомните разницу:
REPEAT просто повторяет строку заданное количество раз;
LPAD дополняет значение слева до нужной длины;
RPAD дополняет значение справа до нужной длины.
REPEAT не создаёт строки таблицы
Ещё одна частая путаница: REPEAT повторяет текст внутри одной строки, но не создаёт несколько строк результата.
SELECT REPEAT('row', 3);
Результат:
rowrowrow
Если вам нужны три отдельные строки, в PostgreSQL используйте generate_series:
SELECT 'row' AS value
FROM generate_series(1, 3);
Результат:
value
-----
row
row
row
Это разные задачи.
REPEAT — для строкового форматирования.
generate_series — для генерации строк в результате запроса.
Практический пример: отчёт по отделам
Допустим, есть таблица сотрудников:
dept | salary
------------+-------
Engineering | 5200
Engineering | 4800
Marketing | 3100
Marketing | 2900
Support | 2200
Хотим вывести среднюю зарплату и маленькую диаграмму:
SELECT
dept,
ROUND(AVG(salary)) AS avg_salary,
REPEAT('#', ROUND(AVG(salary) / 1000)::int) AS salary_bar
FROM employees
GROUP BY dept
ORDER BY avg_salary DESC;
Результат:
dept | avg_salary | salary_bar
------------+------------+-----------
Engineering | 5000 | #####
Marketing | 3000 | ###
Support | 2200 | ##
Такой запрос сразу показывает не только числа, но и визуальное сравнение.
Для серьёзной аналитики лучше использовать нормальные графики. Но для быстрой проверки данных в SQL-консоли это очень удобно.
Практический пример: линия под заголовком
Допустим, нужно вывести название курса и линию под ним:
SELECT
title,
REPEAT('-', LENGTH(title)) AS underline
FROM courses;
Если данные такие:
title
----------------
SQL Basics
Window Functions
результат будет таким:
title | underline
-----------------+-----------------
SQL Basics | ----------
Window Functions | ----------------
Линия автоматически подстраивается под длину заголовка. Если название изменится, длина подчёркивания тоже изменится.
Практический пример: шкала прогресса
Допустим, у пользователя есть прогресс по курсу от 0 до 100.
user_name | progress
----------+---------
Alex | 80
Maria | 45
Ivan | 10
Можно вывести простую шкалу из 10 делений:
SELECT
user_name,
progress,
'[' ||
RPAD(REPEAT('#', (progress / 10)::int), 10, '.') ||
']' AS progress_bar
FROM users_progress
ORDER BY progress DESC;
Результат:
user_name | progress | progress_bar
----------+----------+--------------
Alex | 80 | [########..]
Maria | 45 | [####......]
Ivan | 10 | [#.........]
Здесь REPEAT('#', (progress / 10)::int) рисует заполненную часть шкалы, а RPAD(..., 10, '.') добивает её точками до 10 символов.
Получается компактный текстовый прогресс-бар.
Коротко
REPEAT — это функция, которая повторяет строку указанное количество раз.
SELECT REPEAT('ab', 3);
Результат:
ababab
Она полезна, когда нужно:
- сделать разделитель для отчёта;
- добавить отступ;
- собрать повторяющийся текстовый фрагмент;
- подготовить плейсхолдеры вида
?,?,?;
- нарисовать простую текстовую диаграмму;
- сделать рейтинг звёздочками;
- собрать прогресс-бар прямо в SQL.
Главные правила:
REPEAT возвращает одну строку, а не несколько строк таблицы;
- повторять можно не только символ, но и целую строку;
- если количество повторов равно
0, получится пустая строка;
- если количество повторов отрицательное, в PostgreSQL и MySQL обычно тоже получится пустая строка;
- если строка или количество повторов равны
NULL, результат будет NULL;
- слишком большое количество повторов может создать огромную строку;
- для дополнения значения до нужной длины чаще подходят
LPAD и RPAD;
- для генерации нескольких строк нужен не
REPEAT, а другие инструменты, например generate_series.
Самое простое правило: используйте REPEAT, когда нужно не изменить данные, а красиво собрать текстовое представление прямо в запросе.
Иногда в SQL нужно не искать данные и не считать сумму, а просто аккуратно собрать строку. Например, нарисовать разделитель из дефисов, добавить отступ, сделать маленькую текстовую диаграмму или собрать список плейсхолдеров для динамического запроса.
Для таких задач есть функция
REPEAT.Она делает ровно то, что написано в названии: повторяет строку нужное количество раз.
SELECT REPEAT('-', 10) AS line;Результат:
Мы попросили SQL повторить символ
-десять раз. Получилась одна строка из десяти дефисов.Функция простая, но полезная. Особенно когда количество повторов не хочется писать руками, а нужно вычислить его из данных: длины имени, количества пользователей, размера метрики или числа параметров.
Что делает REPEAT
REPEAT(str, n)повторяет строкуstrровноnраз подряд и возвращает результат как одну строку.SELECT REPEAT('ab', 3) AS result;Результат:
Важно: результат — это одна строка, а не несколько строк таблицы.
То есть
REPEAT('ab', 3)возвращает:А не:
Если вам нужно создать несколько строк в результате запроса, для этого используют другие инструменты, например
generate_seriesв PostgreSQL.REPEATнужен именно для повторения текста внутри одного значения.Базовый синтаксис
У функции два аргумента:
Первый аргумент — что повторяем.
Второй аргумент — сколько раз повторяем.
Примеры:
SELECT REPEAT('x', 5) AS result;Результат:
SELECT REPEAT('SQL ', 3) AS result;Результат:
SELECT REPEAT('=-', 4) AS result;Результат:
Повторять можно не только один символ, но и целую строку. Это удобно, когда нужен узор, отступ, разделитель или повторяющийся фрагмент текста.
REPEAT возвращает строку
Даже если вы повторяете цифру, результат всё равно будет строкой, а не числом.
SELECT REPEAT('0', 5) AS result;Результат:
Это не число
0. Это текстовая строка из пяти нулей.Эта разница важна. Например, строку
00000можно склеивать с другими строками:SELECT 'USR-' || REPEAT('0', 3) || '42' AS user_code;Результат:
Но для настоящего форматирования кодов чаще используют
LPAD, потому что он не просто повторяет символ, а дополняет конкретное значение до нужной длины.REPEAT и обычная склейка строк
REPEATчасто используют вместе с оператором||, который склеивает строки в PostgreSQL.Например, сделаем отступ перед текстом:
SELECT REPEAT(' ', 4) || 'Hello' AS result;Результат:
Здесь
REPEAT(' ', 4)создаёт четыре пробела, а потом мы приклеиваем к ним словоHello.Такой приём можно использовать для простого форматирования текстовых отчётов.
Разделители для отчётов
Самый понятный сценарий для
REPEAT— сделать ровную линию-разделитель.Например:
SELECT REPEAT('-', 30) AS line;Результат:
Такой разделитель можно использовать в текстовом отчёте:
SELECT REPEAT('=', 40) AS report_line UNION ALL SELECT ' MONTHLY REVENUE REPORT' UNION ALL SELECT REPEAT('=', 40);Результат:
Конечно, современные отчёты чаще строятся в BI-системах, Excel, HTML или интерфейсе приложения. Но для консольных отчётов, технических выгрузок, логов и отладочных запросов такие приёмы всё ещё бывают полезны.
Главное преимущество здесь в том, что вы не пишете руками строку из сорока символов. Вы явно говорите
REPEAT('=', 40). Это читается проще и выглядит аккуратнее.Разделитель по длине значения
Иногда хочется, чтобы длина линии зависела от данных.
Например, нужно вывести имя и под ним линию такой же длины:
SELECT name, REPEAT('-', LENGTH(name)) AS underline FROM users ORDER BY name;Если в таблице есть такие имена:
результат будет таким:
Здесь
LENGTH(name)считает длину имени, аREPEAT('-', LENGTH(name))создаёт линию такой же длины.Это хороший пример того, зачем вообще нужен
REPEAT: количество повторов можно не хардкодить, а вычислять прямо из данных.Отступы и простое форматирование
REPEATудобно использовать для отступов.Например, представим таблицу категорий, где есть уровень вложенности:
Можно вывести категории с отступами:
SELECT REPEAT(' ', level) || name AS category_tree FROM categories ORDER BY id;Результат:
Здесь мы повторяем два пробела столько раз, сколько указано в колонке
level.Если
level = 0, отступа нет.Если
level = 1, будет два пробела.Если
level = 2, будет четыре пробела.Получается простой текстовый вид дерева прямо в SQL.
Плейсхолдеры для динамических запросов
Ещё один практический сценарий — собрать строку с плейсхолдерами.
Например, приложению нужно подготовить SQL-запрос с условием
IN, где количество значений заранее неизвестно.Для трёх значений нужен такой фрагмент:
Его можно собрать через
REPEAT:SELECT RTRIM(REPEAT('?,', 3), ',') AS placeholders;Результат:
Как это работает:
REPEAT('?,', 3)сначала создаёт строку:А потом
RTRIM(..., ',')убирает лишнюю запятую справа.В итоге остаётся:
Такой приём удобен, когда повторяется один и тот же элемент с разделителем: «плейсхолдер плюс запятая».
Почему мы сначала добавляем лишнюю запятую
На первый взгляд странно: зачем сначала делать лишнюю запятую, а потом её удалять?
Потому что так проще.
Если собирать строку вручную, постоянно появляется проблема последнего элемента:
После первых двух плейсхолдеров запятая нужна, а после последнего — нет.
В программировании это частая задача. Один из простых способов её решить: повторять фрагмент вместе с разделителем, а потом убрать последний разделитель.
SELECT RTRIM(REPEAT('?, ', 4), ', ') AS placeholders;Результат:
Здесь строка сначала получится такой:
А потом хвостовой разделитель будет убран.
Важное предупреждение про динамический SQL
REPEATможет помочь собрать форму строки, например?,?,?.Но он не должен использоваться для склейки пользовательских значений прямо в SQL.
Плохая идея:
-- never concatenate raw user input into SQL SELECT 'SELECT * FROM users WHERE id IN (' || user_input || ')';Если
user_inputпришёл от пользователя, в нём может оказаться что угодно. Это риск SQL-инъекции.Правильный подход: значения должны передаваться как параметры, а плейсхолдеры должны только обозначать места для этих параметров.
То есть
REPEATможет помочь собрать каркас:Но сами значения должны безопасно подставляться через механизм параметров в приложении или драйвере базы данных.
Нумерованные плейсхолдеры в PostgreSQL
В PostgreSQL часто используются нумерованные плейсхолдеры:
Для них
REPEATуже не так удобен, потому что каждый элемент отличается номером.Если нужно собрать именно такую строку внутри PostgreSQL, лучше использовать
generate_seriesиstring_agg:SELECT string_agg('$' || n, ', ' ORDER BY n) AS placeholders FROM generate_series(1, 3) AS g(n);Результат:
Это важное отличие.
REPEATхорош, когда повторяется одинаковый фрагмент:А если каждый фрагмент отличается:
лучше использовать генерацию строк и агрегацию.
Текстовые бар-чарты
Один из самых приятных примеров
REPEAT— маленькие текстовые диаграммы прямо в результате SQL-запроса.Допустим, у нас есть пользователи по странам. Мы хотим не только вывести количество, но и показать простой визуальный столбик.
SELECT country, COUNT(*) AS users_count, REPEAT('#', (COUNT(*) / 10)::int) AS bar FROM users GROUP BY country ORDER BY users_count DESC;Пример результата:
Здесь один символ
#означает примерно 10 пользователей.Если пользователей 52, получится 5 символов.
Если 31 — 3 символа.
Если 18 — 1 символ.
Это не замена настоящей визуализации, но для быстрого анализа в консоли очень удобно.
Как выбрать масштаб для бара
В текстовой диаграмме важно правильно выбрать масштаб.
Если написать так:
SELECT REPEAT('#', COUNT(*)::int) FROM users GROUP BY country;и в стране 5000 пользователей, SQL нарисует строку из 5000 символов. Это будет неудобно и некрасиво.
Поэтому метрику обычно делят:
SELECT REPEAT('#', (COUNT(*) / 10)::int) FROM users GROUP BY country;или:
SELECT REPEAT('#', (COUNT(*) / 100)::int) FROM users GROUP BY country;Масштаб зависит от данных.
Например:
Хорошая диаграмма должна помещаться на экране и помогать сравнивать значения, а не превращаться в бесконечную строку.
Ограничение максимальной длины бара
Чтобы случайно не получить слишком длинную строку, можно ограничить количество символов сверху через
LEAST.Например:
SELECT country, COUNT(*) AS users_count, REPEAT('#', LEAST((COUNT(*) / 10)::int, 30)) AS bar FROM users GROUP BY country ORDER BY users_count DESC;Здесь бар не будет длиннее 30 символов.
Даже если пользователей очень много, строка останется аккуратной.
Запись
LEAST((COUNT(*) / 10)::int, 30)означает: взять меньшее из двух значений — рассчитанную длину бара или 30.Это полезная защита для отчётов, особенно если данные могут резко вырасти.
Минимальный видимый бар
Есть и обратная проблема.
Если значение маленькое, после деления может получиться 0.
Например,
18 / 100при целочисленном делении даёт0.Тогда
REPEAT('#', 0)вернёт пустую строку, и визуально покажется, будто значения вообще нет.Если вам нужно показывать хотя бы один символ для ненулевых значений, можно использовать
GREATEST.Например:
SELECT country, COUNT(*) AS users_count, REPEAT('#', GREATEST((COUNT(*) / 10)::int, 1)) AS bar FROM users GROUP BY country ORDER BY users_count DESC;Но тут есть нюанс: если значение действительно равно нулю, такой запрос всё равно нарисует один символ. Поэтому для точной логики лучше использовать
CASE.SELECT country, COUNT(*) AS users_count, CASE WHEN COUNT(*) = 0 THEN '' ELSE REPEAT('#', GREATEST((COUNT(*) / 10)::int, 1)) END AS bar FROM users GROUP BY country ORDER BY users_count DESC;Так мы говорим:
REPEAT вместе с LPAD и RPAD
REPEATчасто используют вместе сLPADиRPAD.REPEATрисует сам бар:SELECT REPEAT('#', 5);Результат:
А
LPADилиRPADпомогают выровнять его до нужной ширины.Например, выведем среднюю зарплату по отделам:
SELECT dept, ROUND(AVG(salary)) AS avg_salary, RPAD(REPEAT('#', (AVG(salary) / 1000)::int), 30, ' ') AS bar FROM employees GROUP BY dept ORDER BY avg_salary DESC;Результат может выглядеть так:
Здесь
REPEAT('#', (AVG(salary) / 1000)::int)создаёт бар, аRPAD(..., 30, ' ')дополняет его пробелами справа до 30 символов, чтобы все строки занимали одинаковую ширину.Если хочется выровнять бар вправо, можно использовать
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можно использовать для простых рейтингов.Допустим, в таблице товаров есть рейтинг от 1 до 5:
Можно вывести рейтинг звёздочками:
Результат:
Такой формат удобен для демонстрационных отчётов и учебных задач.
Если нужно всегда показывать шкалу из пяти символов, можно добавить пустые звёзды:
Результат:
Вот здесь
REPEATуже помогает собрать не просто повтор, а маленький понятный визуальный элемент.Что будет, если количество повторов равно нулю
Если количество повторов равно
0, функция вернёт пустую строку.SELECT REPEAT('x', 0) AS result;Результат:
Там не будет ни одного символа.
Это не
NULLи не ошибка. Это просто пустая строка.В текстовых диаграммах это важно: если значение после расчёта превратилось в 0, столбик исчезнет.
Например:
SELECT REPEAT('#', (5 / 10)::int) AS bar;Результат будет пустым, потому что
5 / 10при целочисленном расчёте может дать0.Поэтому для баров часто используют
GREATEST, если нужно показать хотя бы один символ.Что будет, если количество повторов отрицательное
В PostgreSQL и MySQL отрицательное количество повторов обычно даёт пустую строку.
SELECT REPEAT('x', -3) AS result;Результат:
Ошибки нет, но и символов тоже нет.
Это может быть неожиданно.
Например, если вы считаете длину бара по формуле
value - 10иvalueоказался меньше 10, количество повторов станет отрицательным. В результате бар просто исчезнет.Чтобы защититься, можно использовать
GREATEST:SELECT REPEAT('#', GREATEST(value - 10, 0)) AS bar FROM metrics;Так мы говорим: если расчёт дал отрицательное число, возьми 0.
Что будет с NULL
Если строка или количество повторов равны
NULL, результат тоже будетNULL.SELECT REPEAT(NULL, 3) AS result;Результат:
SELECT REPEAT('x', NULL) AS result;Результат:
Это логично: если SQL не знает, что повторять или сколько раз повторять, он не может собрать строку.
Если в данных может быть
NULL, используйтеCOALESCE.Например:
SELECT REPEAT('#', COALESCE(score, 0)) AS bar FROM metrics;Здесь
COALESCE(score, 0)означает: еслиscoreравенNULL, возьми0.Тогда вместо
NULLполучится пустая строка.Если нужно заменить
NULLв самой строке:SELECT REPEAT(COALESCE(label, ''), 3) AS repeated_label FROM labels;Дробное количество повторов
Количество повторов должно быть целым числом.
Но в реальных запросах вы часто получаете дробное значение после деления или среднего значения.
Например, выражение
AVG(salary) / 1000может быть дробным.Чтобы передать это значение в
REPEAT, лучше явно привести его к целому типу.В PostgreSQL:
SELECT REPEAT('#', (AVG(salary) / 1000)::int) AS bar FROM employees;Так запрос читается понятнее: мы явно показываем, что количество символов должно быть целым.
Но помните: приведение к
intможет округлять значение. Поэтому для отчётов лучше заранее решить, какая логика вам нужна:Например:
SELECT REPEAT('#', LEAST(ROUND(AVG(salary) / 1000)::int, 30)) AS bar FROM employees;Здесь мы округляем значение, приводим его к
intи ограничиваем бар 30 символами.Не давайте REPEAT бесконтрольно раздувать строку
REPEATможет создать очень большую строку.Например:
SELECT REPEAT('ab', 1000000);Это строка на два миллиона символов.
В учебном примере это просто странно. А в реальном запросе такое может нагрузить память, сеть и клиент, который получает результат.
Особенно осторожно нужно быть, если количество повторов приходит из данных или пользовательского ввода.
Плохая идея:
SELECT REPEAT('#', user_provided_number) AS bar FROM metrics;Если
user_provided_number = 10000000, результат будет огромным.Лучше ограничивать длину:
SELECT REPEAT('#', LEAST(user_provided_number, 100)) AS bar FROM metrics;Для текстовых диаграмм обычно достаточно 30, 50 или 100 символов. Всё, что больше, уже плохо читается.
REPEAT в PostgreSQL
В PostgreSQL функция называется
repeat.Обычно её пишут в верхнем регистре в статьях и примерах:
SELECT REPEAT('ha', 3) AS result;Результат:
Но в PostgreSQL имена функций не чувствительны к регистру, поэтому эти варианты эквивалентны:
SELECT repeat('ha', 3); SELECT REPEAT('ha', 3);Для строковой склейки в PostgreSQL часто используют оператор
||:SELECT REPEAT('-', 10) || ' report ' || REPEAT('-', 10) AS title;Результат:
REPEAT в MySQL
В MySQL функция тоже называется
REPEAT.Синтаксис такой же:
SELECT REPEAT('ab', 3) AS result;Результат:
Для склейки строк в MySQL обычно используют
CONCAT, а не||:SELECT CONCAT(REPEAT('-', 10), ' report ', REPEAT('-', 10)) AS title;Результат:
Это важное отличие для новичков: сама функция
REPEATпохожа, но способы склеивания строк в разных СУБД могут отличаться.REPEAT в ClickHouse
В ClickHouse тоже есть функция
repeat.Пример:
SELECT repeat('ab', 3) AS result;Результат:
Для текстовых отчётов и простых баров идея та же:
SELECT repeat('#', 5) AS bar;Результат:
При переносе запросов между PostgreSQL, MySQL и ClickHouse стоит отдельно проверить граничные случаи:
0, отрицательные значения,NULLи слишком большие числа. На обычных примерах функция выглядит одинаково, а различия чаще появляются именно на краях.REPEAT не заменяет LPAD и RPAD
Иногда новички пытаются использовать
REPEATтам, где лучше подходитLPADилиRPAD.Например, нужно превратить
42в000042.Через
REPEATможно попытаться сделать так:SELECT REPEAT('0', 4) || '42' AS code;Результат:
Но это работает только потому, что мы заранее знаем: нужно ровно четыре нуля.
Если значение будет другим, например
7или12345, количество нулей уже нужно считать отдельно.Для таких задач лучше использовать
LPAD:SELECT LPAD('42', 6, '0') AS code;Результат:
LPADсам понимает, сколько символов нужно добавить слева до нужной длины.Запомните разницу:
REPEATпросто повторяет строку заданное количество раз;LPADдополняет значение слева до нужной длины;RPADдополняет значение справа до нужной длины.REPEAT не создаёт строки таблицы
Ещё одна частая путаница:
REPEATповторяет текст внутри одной строки, но не создаёт несколько строк результата.SELECT REPEAT('row', 3);Результат:
Если вам нужны три отдельные строки, в PostgreSQL используйте
generate_series:SELECT 'row' AS value FROM generate_series(1, 3);Результат:
Это разные задачи.
REPEAT— для строкового форматирования.generate_series— для генерации строк в результате запроса.Практический пример: отчёт по отделам
Допустим, есть таблица сотрудников:
Хотим вывести среднюю зарплату и маленькую диаграмму:
SELECT dept, ROUND(AVG(salary)) AS avg_salary, REPEAT('#', ROUND(AVG(salary) / 1000)::int) AS salary_bar FROM employees GROUP BY dept ORDER BY avg_salary DESC;Результат:
Такой запрос сразу показывает не только числа, но и визуальное сравнение.
Для серьёзной аналитики лучше использовать нормальные графики. Но для быстрой проверки данных в SQL-консоли это очень удобно.
Практический пример: линия под заголовком
Допустим, нужно вывести название курса и линию под ним:
SELECT title, REPEAT('-', LENGTH(title)) AS underline FROM courses;Если данные такие:
результат будет таким:
Линия автоматически подстраивается под длину заголовка. Если название изменится, длина подчёркивания тоже изменится.
Практический пример: шкала прогресса
Допустим, у пользователя есть прогресс по курсу от 0 до 100.
Можно вывести простую шкалу из 10 делений:
SELECT user_name, progress, '[' || RPAD(REPEAT('#', (progress / 10)::int), 10, '.') || ']' AS progress_bar FROM users_progress ORDER BY progress DESC;Результат:
Здесь
REPEAT('#', (progress / 10)::int)рисует заполненную часть шкалы, аRPAD(..., 10, '.')добивает её точками до 10 символов.Получается компактный текстовый прогресс-бар.
Коротко
REPEAT— это функция, которая повторяет строку указанное количество раз.SELECT REPEAT('ab', 3);Результат:
Она полезна, когда нужно:
?,?,?;Главные правила:
REPEATвозвращает одну строку, а не несколько строк таблицы;0, получится пустая строка;NULL, результат будетNULL;LPADиRPAD;REPEAT, а другие инструменты, напримерgenerate_series.Самое простое правило: используйте
REPEAT, когда нужно не изменить данные, а красиво собрать текстовое представление прямо в запросе.