RANDOM() в PostgreSQL возвращает псевдослучайное число с плавающей точкой в диапазоне от 0 включительно до 1 не включительно.
Проще говоря, это дробь вида 0.8473920192, 0.15412003, 0.9991 — каждый раз новая. На такой маленькой функции держится много полезных вещей: случайная выборка пользователей, перемешивание строк, A/B-группы, демоданные, тестовые сценарии и простые «лотереи» внутри SQL.
Например, вы хотите взять пять случайных пользователей на ручную проверку. Или случайно распределить заказы по трём обработчикам. Или показать на витрине товары в разном порядке, чтобы страница не выглядела одинаково каждый раз. Во всех этих задачах рядом почти всегда появляется random().
Разберёмся спокойно: как она работает, как получать случайные строки и числа, почему ORDER BY random() иногда становится дорогим, чем помогает TABLESAMPLE и как сделать случайность повторяемой для тестов.
Базовый пример: что возвращает RANDOM
Самый простой запрос:
SELECT random();
Результат будет примерно таким:
0.8473920192
Точное число каждый раз будет другим. Диапазон у функции такой:
[0, 1)
Это значит:
0 теоретически возможно;
1 уже не входит в диапазон;
- все значения находятся где-то между
0 и 0.999999....
Важно: каждый вызов random() считается отдельно.
SELECT random() AS r1,
random() AS r2;
Даже в одной строке r1 и r2 почти всегда будут разными, потому что это два разных вызова функции.
Эта деталь кажется мелкой, но на ней строятся почти все приёмы со случайной сортировкой.
Как выбрать случайные строки из таблицы
Классический способ получить несколько случайных строк — отсортировать таблицу по random() и взять первые строки через LIMIT.
SELECT id, email, country
FROM users
ORDER BY random()
LIMIT 5;
Что здесь происходит:
- PostgreSQL берёт строки из таблицы
users.
- Для каждой строки вычисляет своё случайное число.
- Сортирует строки по этим случайным числам.
- Возвращает первые
5 строк.
Представьте, что каждой строке выдали случайный билетик. Потом все строки разложили по номеру билетика и взяли первые пять. При следующем запуске билетики будут другими — значит, и результат изменится.
Такой запрос отлично читается. Новичок почти сразу понимает идею: «отсортируй случайно и возьми верхушку».
Можно перемешать всю таблицу целиком:
SELECT id, email, country
FROM users
ORDER BY random();
А можно использовать этот приём для случайной проверки заказов:
SELECT id, customer_id, amount, status
FROM orders
WHERE status = 'paid'
ORDER BY random()
LIMIT 10;
Например, команда поддержки хочет каждый день вручную просматривать десять случайных оплаченных заказов. Запрос выше как раз делает такую выборку.
Почему ORDER BY RANDOM может быть дорогим
На маленьких таблицах ORDER BY random() обычно работает быстро и не доставляет проблем. Но на больших данных у этого способа есть неприятная цена.
Допустим, в таблице users десять миллионов строк, а вы просите всего пять случайных пользователей:
SELECT id, email
FROM users
ORDER BY random()
LIMIT 5;
Кажется, что раз нужен только LIMIT 5, база должна быстро найти пять строк и остановиться. Но нет.
Чтобы честно отсортировать строки по случайному значению, PostgreSQL должен:
- пройти по большому количеству строк;
- для каждой строки вычислить
random();
- отсортировать результат;
- только потом взять первые
5.
То есть стоимость запроса зависит не только от LIMIT, а от размера таблицы.
Это главный подвох. Вы просите пять строк, но база всё равно делает большую работу.
Индекс здесь обычно не спасает, потому что значение random() каждый раз новое. Нельзя заранее построить полезный индекс по числу, которое пересчитывается при каждом запуске запроса.
На большой таблице такой запрос может превратиться в полный проход по данным и тяжёлую сортировку. В EXPLAIN ANALYZE вы часто увидите что-то вроде полного чтения таблицы и узла сортировки. Это сигнал: для маленькой таблицы нормально, для огромной — уже опасно.
Когда ORDER BY RANDOM подходит
ORDER BY random() — не плохой приём. Он просто не универсальный.
Его удобно использовать, когда:
- таблица маленькая;
- запрос запускается редко;
- нужна простая и понятная случайная выборка;
- вы делаете демо, учебный пример или разовую проверку;
- точность важнее скорости.
Например, для таблицы из нескольких тысяч строк это вполне нормальный вариант:
SELECT id, title
FROM lessons
ORDER BY random()
LIMIT 3;
Если это учебный сайт и вы хотите показать три случайных урока из небольшой таблицы, запрос будет простым и понятным.
А вот если таблица содержит миллионы или десятки миллионов строк, лучше подумать о других подходах.
TABLESAMPLE: быстрый способ взять примерную выборку
В PostgreSQL есть конструкция TABLESAMPLE. Она позволяет взять случайную часть таблицы без полной сортировки результата.
Пример:
SELECT id, amount, status
FROM orders TABLESAMPLE SYSTEM (1);
Здесь SYSTEM (1) означает примерно 1% таблицы.
Есть два распространённых метода:
SELECT id, amount, status
FROM orders TABLESAMPLE BERNOULLI (5);
SELECT id, amount, status
FROM orders TABLESAMPLE SYSTEM (5);
Разница между ними важная.
BERNOULLI рассматривает строки более независимо: у каждой строки есть шанс попасть в выборку. Это ближе к «честной» случайной выборке, но может быть дороже.
SYSTEM работает грубее: он сэмплирует блоки данных. Поэтому он часто быстрее, но выборка может быть менее равномерной. Если рядом физически лежат похожие строки, они могут попадать в выборку пачками.
Для больших таблиц SYSTEM часто используют как быстрый черновой способ: «дай мне примерно немного случайных данных, без тяжёлой сортировки всей таблицы».
TABLESAMPLE не гарантирует точное число строк
Важный момент: TABLESAMPLE возвращает примерный процент, а не точное количество строк.
Вот запрос:
SELECT id, email
FROM users TABLESAMPLE SYSTEM (10);
Он не означает «верни ровно десять строк». Он означает «возьми примерно 10% таблицы по выбранному методу сэмплинга».
Поэтому результат может быть разным:
- в одном запуске вернулось
958 строк;
- в другом
1031;
- в третьем
990.
Это нормально.
Если вам нужно ровно 5 случайных строк из большой таблицы, можно совместить два подхода:
SELECT id, email
FROM users TABLESAMPLE SYSTEM (10)
ORDER BY random()
LIMIT 5;
Идея такая:
- Сначала
TABLESAMPLE SYSTEM (10) быстро берёт примерную часть таблицы.
- Потом
ORDER BY random() сортирует уже не всю таблицу, а только эту меньшую выборку.
LIMIT 5 оставляет ровно пять строк.
Это не всегда идеально с точки зрения статистики, зато часто сильно дешевле, чем сортировать огромную таблицу целиком.
Как получить случайное целое число
random() возвращает дробь. Но в реальных задачах часто нужно целое число.
Например:
- случайный номер от
1 до 6, как бросок кубика;
- случайная скидка;
- случайный бакет;
- случайный номер группы.
Базовая формула такая:
SELECT floor(random() * 100)::int AS n;
Этот запрос вернёт целое число от 0 до 99.
Почему именно так?
random() даёт число от 0 до почти 1.
Умножаем на 100:
0 <= random() * 100 < 100
Получаем число от 0 до 99.999....
Затем floor() отбрасывает дробную часть:
0, 1, 2, 3, ..., 99
В итоге получается диапазон:
[0, 100)
То есть 0 включается, 100 не включается.
Пример с кубиком
Чтобы получить число от 1 до 6, используем такую формулу:
SELECT floor(random() * 6)::int + 1 AS dice;
Разбор:
random() * 6
даёт число от 0 до почти 6.
floor(random() * 6)::int
даёт целое число от 0 до 5.
floor(random() * 6)::int + 1
даёт целое число от 1 до 6.
То есть получается обычный шестигранный кубик.
Случайное распределение по группам
Допустим, у нас есть сотрудники, и мы хотим случайно разложить их по трём группам: 0, 1, 2.
SELECT id,
name,
dept,
floor(random() * 3)::int AS bucket
FROM employees;
Результат может быть таким:
id | name | dept | bucket
---+-------+---------+--------
1 | Alex | sales | 2
2 | Maria | support | 0
3 | Ivan | qa | 1
4 | Nina | sales | 2
При следующем запуске бакеты, скорее всего, изменятся.
Такой подход удобно использовать для демоданных или разовых случайных распределений. Но если вы делаете серьёзный A/B-тест, где пользователь должен всегда попадать в одну и ту же группу, лучше использовать детерминированный способ: например, хеш от user_id.
Иначе сегодня пользователь попадёт в группу A, завтра в B, а послезавтра снова в A. Для эксперимента это плохо.
Почему лучше не использовать ROUND для случайных целых
Иногда хочется написать так:
SELECT round(random() * 6)::int AS n;
Но для равномерного случайного целого числа это плохая идея.
Причина в том, что round() округляет к ближайшему целому. Из-за этого крайние значения получают меньше шансов.
Например, если вы делаете:
SELECT round(random() * 6)::int AS n;
то результат может быть от 0 до 6. Но числа 0 и 6 выпадают реже, чем 1, 2, 3, 4, 5.
Почему?
Для 0 подходит только маленький кусок диапазона: примерно от 0 до 0.5.
Для 1 подходит уже больший кусок: примерно от 0.5 до 1.5.
То есть середина получает больше места, а края — меньше.
Если вам нужно равномерное распределение, используйте floor():
SELECT floor(random() * 6)::int + 1 AS n;
Это привычная и правильная формула.
Случайные значения в INSERT
random() удобно использовать, когда нужно быстро накидать демоданные.
Например, создадим несколько тестовых платежей со случайной суммой:
INSERT INTO payments (user_id, amount)
SELECT id,
floor(random() * 9000)::int + 1000 AS amount
FROM users
LIMIT 20;
Здесь сумма будет от 1000 до 9999.
Разбор формулы:
floor(random() * 9000)::int
даёт число от 0 до 8999.
floor(random() * 9000)::int + 1000
даёт число от 1000 до 9999.
Так можно быстро оживить учебную базу: вместо одинаковых значений появляются разные суммы, рейтинги, скидки, баллы и другие тестовые числа.
Случайное обновление строк
random() можно использовать и в UPDATE.
Например, назначим пользователям случайный рейтинг от 1 до 5:
UPDATE users
SET rating = floor(random() * 5)::int + 1;
Каждая строка получит своё значение, потому что random() будет вызван для каждой обновляемой строки.
Можно обновить не всех, а только часть:
UPDATE users
SET rating = floor(random() * 5)::int + 1
WHERE country = 'BR';
Такой запрос назначит случайный рейтинг только пользователям из выбранной страны.
Но с обновлениями будьте аккуратны: случайность легко испортит данные, если забыть WHERE. Перед настоящим UPDATE лучше сначала проверить выборку через SELECT.
Повторяемая случайность через SETSEED
Случайность хороша, пока вам не нужны стабильные тесты.
Представьте, что у вас есть автотест. Он запускает запрос со случайной сортировкой и проверяет результат. Сегодня тест прошёл, завтра упал, потому что строки пришли в другом порядке. Неприятно.
Для таких случаев в PostgreSQL есть setseed().
SELECT setseed(0.42);
SELECT id
FROM users
ORDER BY random()
LIMIT 3;
setseed() задаёт начальное состояние генератора случайных чисел для текущей сессии. После этого последовательность random() становится воспроизводимой.
То есть если вы снова установите тот же сид и выполните тот же запрос в тех же условиях, порядок будет повторяться.
SELECT setseed(0.42);
SELECT random() AS r1,
random() AS r2,
random() AS r3;
Если повторить это в той же среде, последовательность будет одинаковой.
Важные ограничения SETSEED
У setseed() есть несколько важных деталей.
Во-первых, значение должно быть в диапазоне от -1 до 1.
SELECT setseed(0.7);
Так можно.
SELECT setseed(42);
Так нельзя: 42 выходит за допустимый диапазон.
Во-вторых, сид действует только в рамках текущего соединения с базой. Если вы открыли новое подключение, оно не обязано помнить старый setseed().
В-третьих, повторяемость полезна для тестов и демо, но не стоит воспринимать random() как криптографически безопасную случайность. Для паролей, токенов, секретных ссылок и похожих задач нужны специальные средства, а не обычный random().
RANDOM и A/B-тесты
Для простого одноразового распределения можно написать так:
SELECT id,
email,
CASE
WHEN random() < 0.5 THEN 'A'
ELSE 'B'
END AS test_group
FROM users;
Запрос случайно разложит пользователей на две группы.
Но есть проблема: при каждом запуске группы будут пересчитываться заново.
Сегодня пользователь может попасть в A, а завтра в B. Для настоящего A/B-теста это обычно неправильно. Пользователь должен оставаться в своей группе, иначе результаты эксперимента смешаются.
Для реального A/B-теста лучше один раз записать группу в таблицу:
UPDATE users
SET test_group = CASE
WHEN random() < 0.5 THEN 'A'
ELSE 'B'
END
WHERE test_group IS NULL;
Так группа назначается только тем, у кого её ещё нет. После этого пользователь остаётся в своей группе.
MySQL: RAND вместо RANDOM
В MySQL похожая функция называется RAND().
SELECT RAND();
Диапазон такой же: от 0 включительно до 1 не включительно.
Случайные строки выбирают похожим способом:
SELECT id, email
FROM users
ORDER BY RAND()
LIMIT 5;
И проблема та же: на большой таблице ORDER BY RAND() может быть дорогим, потому что базе нужно вычислить случайное значение для строк и отсортировать результат.
Для повторяемой случайности в MySQL можно передать сид прямо в функцию:
SELECT RAND(42);
А пример со случайной выборкой может выглядеть так:
SELECT id, email
FROM users
ORDER BY RAND(42)
LIMIT 5;
Но при переносе запросов между PostgreSQL и MySQL важно помнить: в PostgreSQL функция называется random(), а в MySQL — RAND().
ClickHouse: rand возвращает целое число
В ClickHouse тоже есть случайные функции, но поведение отличается.
Функция rand() возвращает не дробь от 0 до 1, а целое число типа UInt32.
SELECT rand();
Если нужно получить значение примерно в диапазоне от 0 до 1, можно разделить результат:
SELECT rand() / 4294967295.0 AS r;
Для случайной выборки в ClickHouse часто используют нативный SAMPLE, если таблица это поддерживает:
SELECT id, amount
FROM orders
SAMPLE 0.1;
Это означает выборку примерно 10% данных. Такой подход обычно лучше, чем сортировать всё по случайному значению.
Частые сценарии использования RANDOM
random() часто применяют не ради «математики», а ради обычных рабочих задач.
Например, взять случайные строки для ручной проверки:
SELECT id, email
FROM users
ORDER BY random()
LIMIT 20;
Случайно перемешать список:
SELECT id, title
FROM articles
ORDER BY random();
Сгенерировать случайную скидку от 5 до 30:
SELECT floor(random() * 26)::int + 5 AS discount_percent;
Разложить строки по десяти бакетам:
SELECT id,
floor(random() * 10)::int AS bucket
FROM events;
Взять примерную выборку из большой таблицы:
SELECT id, created_at, amount
FROM orders TABLESAMPLE SYSTEM (2);
Получить ровно несколько строк из предварительной выборки:
SELECT id, created_at, amount
FROM orders TABLESAMPLE SYSTEM (5)
ORDER BY random()
LIMIT 10;
Главное, что нужно запомнить
random() в PostgreSQL возвращает псевдослучайную дробь в диапазоне от 0 включительно до 1 не включительно.
Для случайных строк самый понятный способ — ORDER BY random() LIMIT n.
SELECT id, email
FROM users
ORDER BY random()
LIMIT 5;
На маленьких таблицах это удобно, читаемо и обычно достаточно быстро. На больших таблицах такой запрос может стать дорогим, потому что PostgreSQL приходится вычислять случайное значение для множества строк и сортировать результат.
Для больших данных смотрите в сторону TABLESAMPLE, особенно если вам подходит примерная выборка.
SELECT id, amount
FROM orders TABLESAMPLE SYSTEM (1);
Для случайных целых чисел используйте floor(), а не round().
SELECT floor(random() * 6)::int + 1 AS dice;
Для повторяемых тестов используйте setseed().
SELECT setseed(0.42);
А при переносе между базами не забывайте про различия: в PostgreSQL — random(), в MySQL — RAND(), в ClickHouse — rand() и отдельный механизм SAMPLE.
Главная мысль простая: random() отлично подходит для понятной случайности в SQL, но на больших таблицах случайность тоже нужно проектировать аккуратно. Чем больше данных, тем важнее не только «получить случайно», но и не заставить базу сортировать весь мир ради пяти строк.
RANDOM()в PostgreSQL возвращает псевдослучайное число с плавающей точкой в диапазоне от0включительно до1не включительно.Проще говоря, это дробь вида
0.8473920192,0.15412003,0.9991— каждый раз новая. На такой маленькой функции держится много полезных вещей: случайная выборка пользователей, перемешивание строк, A/B-группы, демоданные, тестовые сценарии и простые «лотереи» внутри SQL.Например, вы хотите взять пять случайных пользователей на ручную проверку. Или случайно распределить заказы по трём обработчикам. Или показать на витрине товары в разном порядке, чтобы страница не выглядела одинаково каждый раз. Во всех этих задачах рядом почти всегда появляется
random().Разберёмся спокойно: как она работает, как получать случайные строки и числа, почему
ORDER BY random()иногда становится дорогим, чем помогаетTABLESAMPLEи как сделать случайность повторяемой для тестов.Базовый пример: что возвращает RANDOM
Самый простой запрос:
SELECT random();Результат будет примерно таким:
Точное число каждый раз будет другим. Диапазон у функции такой:
Это значит:
0теоретически возможно;1уже не входит в диапазон;0и0.999999....Важно: каждый вызов
random()считается отдельно.SELECT random() AS r1, random() AS r2;Даже в одной строке
r1иr2почти всегда будут разными, потому что это два разных вызова функции.Эта деталь кажется мелкой, но на ней строятся почти все приёмы со случайной сортировкой.
Как выбрать случайные строки из таблицы
Классический способ получить несколько случайных строк — отсортировать таблицу по
random()и взять первые строки черезLIMIT.SELECT id, email, country FROM users ORDER BY random() LIMIT 5;Что здесь происходит:
users.5строк.Представьте, что каждой строке выдали случайный билетик. Потом все строки разложили по номеру билетика и взяли первые пять. При следующем запуске билетики будут другими — значит, и результат изменится.
Такой запрос отлично читается. Новичок почти сразу понимает идею: «отсортируй случайно и возьми верхушку».
Можно перемешать всю таблицу целиком:
SELECT id, email, country FROM users ORDER BY random();А можно использовать этот приём для случайной проверки заказов:
SELECT id, customer_id, amount, status FROM orders WHERE status = 'paid' ORDER BY random() LIMIT 10;Например, команда поддержки хочет каждый день вручную просматривать десять случайных оплаченных заказов. Запрос выше как раз делает такую выборку.
Почему ORDER BY RANDOM может быть дорогим
На маленьких таблицах
ORDER BY random()обычно работает быстро и не доставляет проблем. Но на больших данных у этого способа есть неприятная цена.Допустим, в таблице
usersдесять миллионов строк, а вы просите всего пять случайных пользователей:SELECT id, email FROM users ORDER BY random() LIMIT 5;Кажется, что раз нужен только
LIMIT 5, база должна быстро найти пять строк и остановиться. Но нет.Чтобы честно отсортировать строки по случайному значению, PostgreSQL должен:
random();5.То есть стоимость запроса зависит не только от
LIMIT, а от размера таблицы.Это главный подвох. Вы просите пять строк, но база всё равно делает большую работу.
Индекс здесь обычно не спасает, потому что значение
random()каждый раз новое. Нельзя заранее построить полезный индекс по числу, которое пересчитывается при каждом запуске запроса.На большой таблице такой запрос может превратиться в полный проход по данным и тяжёлую сортировку. В
EXPLAIN ANALYZEвы часто увидите что-то вроде полного чтения таблицы и узла сортировки. Это сигнал: для маленькой таблицы нормально, для огромной — уже опасно.Когда ORDER BY RANDOM подходит
ORDER BY random()— не плохой приём. Он просто не универсальный.Его удобно использовать, когда:
Например, для таблицы из нескольких тысяч строк это вполне нормальный вариант:
SELECT id, title FROM lessons ORDER BY random() LIMIT 3;Если это учебный сайт и вы хотите показать три случайных урока из небольшой таблицы, запрос будет простым и понятным.
А вот если таблица содержит миллионы или десятки миллионов строк, лучше подумать о других подходах.
TABLESAMPLE: быстрый способ взять примерную выборку
В PostgreSQL есть конструкция
TABLESAMPLE. Она позволяет взять случайную часть таблицы без полной сортировки результата.Пример:
SELECT id, amount, status FROM orders TABLESAMPLE SYSTEM (1);Здесь
SYSTEM (1)означает примерно1%таблицы.Есть два распространённых метода:
SELECT id, amount, status FROM orders TABLESAMPLE BERNOULLI (5);SELECT id, amount, status FROM orders TABLESAMPLE SYSTEM (5);Разница между ними важная.
BERNOULLIрассматривает строки более независимо: у каждой строки есть шанс попасть в выборку. Это ближе к «честной» случайной выборке, но может быть дороже.SYSTEMработает грубее: он сэмплирует блоки данных. Поэтому он часто быстрее, но выборка может быть менее равномерной. Если рядом физически лежат похожие строки, они могут попадать в выборку пачками.Для больших таблиц
SYSTEMчасто используют как быстрый черновой способ: «дай мне примерно немного случайных данных, без тяжёлой сортировки всей таблицы».TABLESAMPLE не гарантирует точное число строк
Важный момент:
TABLESAMPLEвозвращает примерный процент, а не точное количество строк.Вот запрос:
SELECT id, email FROM users TABLESAMPLE SYSTEM (10);Он не означает «верни ровно десять строк». Он означает «возьми примерно
10%таблицы по выбранному методу сэмплинга».Поэтому результат может быть разным:
958строк;1031;990.Это нормально.
Если вам нужно ровно
5случайных строк из большой таблицы, можно совместить два подхода:SELECT id, email FROM users TABLESAMPLE SYSTEM (10) ORDER BY random() LIMIT 5;Идея такая:
TABLESAMPLE SYSTEM (10)быстро берёт примерную часть таблицы.ORDER BY random()сортирует уже не всю таблицу, а только эту меньшую выборку.LIMIT 5оставляет ровно пять строк.Это не всегда идеально с точки зрения статистики, зато часто сильно дешевле, чем сортировать огромную таблицу целиком.
Как получить случайное целое число
random()возвращает дробь. Но в реальных задачах часто нужно целое число.Например:
1до6, как бросок кубика;Базовая формула такая:
SELECT floor(random() * 100)::int AS n;Этот запрос вернёт целое число от
0до99.Почему именно так?
random()даёт число от0до почти1.Умножаем на
100:Получаем число от
0до99.999....Затем
floor()отбрасывает дробную часть:В итоге получается диапазон:
То есть
0включается,100не включается.Пример с кубиком
Чтобы получить число от
1до6, используем такую формулу:SELECT floor(random() * 6)::int + 1 AS dice;Разбор:
random() * 6даёт число от
0до почти6.floor(random() * 6)::intдаёт целое число от
0до5.floor(random() * 6)::int + 1даёт целое число от
1до6.То есть получается обычный шестигранный кубик.
Случайное распределение по группам
Допустим, у нас есть сотрудники, и мы хотим случайно разложить их по трём группам:
0,1,2.SELECT id, name, dept, floor(random() * 3)::int AS bucket FROM employees;Результат может быть таким:
При следующем запуске бакеты, скорее всего, изменятся.
Такой подход удобно использовать для демоданных или разовых случайных распределений. Но если вы делаете серьёзный A/B-тест, где пользователь должен всегда попадать в одну и ту же группу, лучше использовать детерминированный способ: например, хеш от
user_id.Иначе сегодня пользователь попадёт в группу
A, завтра вB, а послезавтра снова вA. Для эксперимента это плохо.Почему лучше не использовать ROUND для случайных целых
Иногда хочется написать так:
SELECT round(random() * 6)::int AS n;Но для равномерного случайного целого числа это плохая идея.
Причина в том, что
round()округляет к ближайшему целому. Из-за этого крайние значения получают меньше шансов.Например, если вы делаете:
SELECT round(random() * 6)::int AS n;то результат может быть от
0до6. Но числа0и6выпадают реже, чем1,2,3,4,5.Почему?
Для
0подходит только маленький кусок диапазона: примерно от0до0.5.Для
1подходит уже больший кусок: примерно от0.5до1.5.То есть середина получает больше места, а края — меньше.
Если вам нужно равномерное распределение, используйте
floor():SELECT floor(random() * 6)::int + 1 AS n;Это привычная и правильная формула.
Случайные значения в INSERT
random()удобно использовать, когда нужно быстро накидать демоданные.Например, создадим несколько тестовых платежей со случайной суммой:
INSERT INTO payments (user_id, amount) SELECT id, floor(random() * 9000)::int + 1000 AS amount FROM users LIMIT 20;Здесь сумма будет от
1000до9999.Разбор формулы:
floor(random() * 9000)::intдаёт число от
0до8999.floor(random() * 9000)::int + 1000даёт число от
1000до9999.Так можно быстро оживить учебную базу: вместо одинаковых значений появляются разные суммы, рейтинги, скидки, баллы и другие тестовые числа.
Случайное обновление строк
random()можно использовать и вUPDATE.Например, назначим пользователям случайный рейтинг от
1до5:UPDATE users SET rating = floor(random() * 5)::int + 1;Каждая строка получит своё значение, потому что
random()будет вызван для каждой обновляемой строки.Можно обновить не всех, а только часть:
UPDATE users SET rating = floor(random() * 5)::int + 1 WHERE country = 'BR';Такой запрос назначит случайный рейтинг только пользователям из выбранной страны.
Но с обновлениями будьте аккуратны: случайность легко испортит данные, если забыть
WHERE. Перед настоящимUPDATEлучше сначала проверить выборку черезSELECT.Повторяемая случайность через SETSEED
Случайность хороша, пока вам не нужны стабильные тесты.
Представьте, что у вас есть автотест. Он запускает запрос со случайной сортировкой и проверяет результат. Сегодня тест прошёл, завтра упал, потому что строки пришли в другом порядке. Неприятно.
Для таких случаев в PostgreSQL есть
setseed().SELECT setseed(0.42); SELECT id FROM users ORDER BY random() LIMIT 3;setseed()задаёт начальное состояние генератора случайных чисел для текущей сессии. После этого последовательностьrandom()становится воспроизводимой.То есть если вы снова установите тот же сид и выполните тот же запрос в тех же условиях, порядок будет повторяться.
SELECT setseed(0.42); SELECT random() AS r1, random() AS r2, random() AS r3;Если повторить это в той же среде, последовательность будет одинаковой.
Важные ограничения SETSEED
У
setseed()есть несколько важных деталей.Во-первых, значение должно быть в диапазоне от
-1до1.SELECT setseed(0.7);Так можно.
SELECT setseed(42);Так нельзя:
42выходит за допустимый диапазон.Во-вторых, сид действует только в рамках текущего соединения с базой. Если вы открыли новое подключение, оно не обязано помнить старый
setseed().В-третьих, повторяемость полезна для тестов и демо, но не стоит воспринимать
random()как криптографически безопасную случайность. Для паролей, токенов, секретных ссылок и похожих задач нужны специальные средства, а не обычныйrandom().RANDOM и A/B-тесты
Для простого одноразового распределения можно написать так:
SELECT id, email, CASE WHEN random() < 0.5 THEN 'A' ELSE 'B' END AS test_group FROM users;Запрос случайно разложит пользователей на две группы.
Но есть проблема: при каждом запуске группы будут пересчитываться заново.
Сегодня пользователь может попасть в
A, а завтра вB. Для настоящего A/B-теста это обычно неправильно. Пользователь должен оставаться в своей группе, иначе результаты эксперимента смешаются.Для реального A/B-теста лучше один раз записать группу в таблицу:
UPDATE users SET test_group = CASE WHEN random() < 0.5 THEN 'A' ELSE 'B' END WHERE test_group IS NULL;Так группа назначается только тем, у кого её ещё нет. После этого пользователь остаётся в своей группе.
MySQL: RAND вместо RANDOM
В MySQL похожая функция называется
RAND().SELECT RAND();Диапазон такой же: от
0включительно до1не включительно.Случайные строки выбирают похожим способом:
SELECT id, email FROM users ORDER BY RAND() LIMIT 5;И проблема та же: на большой таблице
ORDER BY RAND()может быть дорогим, потому что базе нужно вычислить случайное значение для строк и отсортировать результат.Для повторяемой случайности в MySQL можно передать сид прямо в функцию:
SELECT RAND(42);А пример со случайной выборкой может выглядеть так:
SELECT id, email FROM users ORDER BY RAND(42) LIMIT 5;Но при переносе запросов между PostgreSQL и MySQL важно помнить: в PostgreSQL функция называется
random(), а в MySQL —RAND().ClickHouse: rand возвращает целое число
В ClickHouse тоже есть случайные функции, но поведение отличается.
Функция
rand()возвращает не дробь от0до1, а целое число типаUInt32.SELECT rand();Если нужно получить значение примерно в диапазоне от
0до1, можно разделить результат:SELECT rand() / 4294967295.0 AS r;Для случайной выборки в ClickHouse часто используют нативный
SAMPLE, если таблица это поддерживает:SELECT id, amount FROM orders SAMPLE 0.1;Это означает выборку примерно
10%данных. Такой подход обычно лучше, чем сортировать всё по случайному значению.Частые сценарии использования RANDOM
random()часто применяют не ради «математики», а ради обычных рабочих задач.Например, взять случайные строки для ручной проверки:
SELECT id, email FROM users ORDER BY random() LIMIT 20;Случайно перемешать список:
SELECT id, title FROM articles ORDER BY random();Сгенерировать случайную скидку от
5до30:SELECT floor(random() * 26)::int + 5 AS discount_percent;Разложить строки по десяти бакетам:
SELECT id, floor(random() * 10)::int AS bucket FROM events;Взять примерную выборку из большой таблицы:
SELECT id, created_at, amount FROM orders TABLESAMPLE SYSTEM (2);Получить ровно несколько строк из предварительной выборки:
SELECT id, created_at, amount FROM orders TABLESAMPLE SYSTEM (5) ORDER BY random() LIMIT 10;Главное, что нужно запомнить
random()в PostgreSQL возвращает псевдослучайную дробь в диапазоне от0включительно до1не включительно.Для случайных строк самый понятный способ —
ORDER BY random() LIMIT n.SELECT id, email FROM users ORDER BY random() LIMIT 5;На маленьких таблицах это удобно, читаемо и обычно достаточно быстро. На больших таблицах такой запрос может стать дорогим, потому что PostgreSQL приходится вычислять случайное значение для множества строк и сортировать результат.
Для больших данных смотрите в сторону
TABLESAMPLE, особенно если вам подходит примерная выборка.SELECT id, amount FROM orders TABLESAMPLE SYSTEM (1);Для случайных целых чисел используйте
floor(), а неround().SELECT floor(random() * 6)::int + 1 AS dice;Для повторяемых тестов используйте
setseed().SELECT setseed(0.42);А при переносе между базами не забывайте про различия: в PostgreSQL —
random(), в MySQL —RAND(), в ClickHouse —rand()и отдельный механизмSAMPLE.Главная мысль простая:
random()отлично подходит для понятной случайности в SQL, но на больших таблицах случайность тоже нужно проектировать аккуратно. Чем больше данных, тем важнее не только «получить случайно», но и не заставить базу сортировать весь мир ради пяти строк.