RANDOM() retorna um valor pseudoaleatorio de ponto flutuante em [0, 1) e esta por tras de quase toda consulta "aleatoria" que voce escreve: escolher um usuario de amostra, distribuir grupos A/B ou embaralhar linhas. Vamos ver como usa-lo bem e onde ele vira um gargalo.
O valor base e linhas aleatorias
No PostgreSQL, RANDOM() gera a cada chamada um numero novo de 0 (incluso) ate, mas sem incluir, 1:
SELECT random();
SELECT random() AS r1, random() AS r2;
A forma classica de pegar linhas aleatorias e ordenar por RANDOM() e tomar o topo:
SELECT id, email, country
FROM users
ORDER BY random()
LIMIT 5;
Cada linha recebe uma chave de ordenacao aleatoria, entao o resultado muda a cada execucao. Em tabelas pequenas isso fica legivel e responde na hora.
Por que ORDER BY RANDOM() e custoso
O pulo do gato e o custo. Para ordenar por RANDOM(), o motor precisa atribuir um numero aleatorio a cada linha e ordenar a tabela inteira antes de aplicar o LIMIT. Em milhoes de linhas isso e um scan completo mais uma ordenacao em disco.
- Um indice sobre a expressao nao ajuda: a chave muda a cada chamada.
- O custo cresce com o tamanho da tabela, nao com o
LIMIT.
Para tabelas grandes, use TABLESAMPLE, que le paginas aleatorias em vez de ordenar tudo:
SELECT id, amount, status
FROM orders TABLESAMPLE BERNOULLI (5);
SELECT * FROM orders TABLESAMPLE SYSTEM (1);
Atencao: TABLESAMPLE devolve uma porcentagem aproximada, nao um numero exato de linhas, e amostra no nivel de pagina, entao a distribuicao nao e perfeitamente uniforme. Quando voce precisa de exatamente N linhas, combine os dois: amostre de forma grosseira e depois refine.
SELECT id, email
FROM users TABLESAMPLE SYSTEM (10)
ORDER BY random()
LIMIT 5;
Inteiros aleatorios
RANDOM() da uma fracao, mas muitas vezes voce precisa de um int num intervalo. A formula e floor(random() * n) para [0, n):
SELECT floor(random() * 100)::int AS n;
SELECT floor(random() * 6)::int + 1 AS dice;
Um uso pratico e espalhar funcionarios em baldes aleatorios ou atribuir um desconto aleatorio:
SELECT id, name, dept,
floor(random() * 3)::int AS shard
FROM employees;
Atencao: nao escreva round(random() * n) para um inteiro uniforme. round puxa para as pontas a metade das vezes, entao as bordas (0 e n) recebem metade da probabilidade. Use sempre floor.
A aleatoriedade atrapalha nos testes: o resultado muda a cada execucao. setseed() fixa o gerador para a sessao atual, recebendo um valor em [-1, 1]:
SELECT setseed(0.42);
SELECT id FROM users ORDER BY random() LIMIT 3;
Depois de setseed, a sequencia de RANDOM() e deterministica ate o proximo setseed ou o fim da sessao, o que e util para testes e demos repetiveis.
MySQL e ClickHouse
A sintaxe varia entre os motores:
- MySQL usa
RAND(), tambem [0, 1). A amostragem e ORDER BY RAND() LIMIT 5; nao existe TABLESAMPLE. A seed vai como argumento: RAND(42).
- ClickHouse tem
rand(), que e um UInt32 em [0, 2^32), nao uma fracao. Para [0,1) divida por 4294967295.0; para amostrar ha um SELECT ... SAMPLE 0.1 nativo.
SELECT id, email FROM users ORDER BY RAND(42) LIMIT 5;
SELECT id, amount FROM orders SAMPLE 0.1;
A licao: ORDER BY RANDOM() e conveniente e correto, mas nao escala. Com dados grandes, migre para amostragem por paginas e reserve setseed/RAND(seed) para uma saida reproduzivel.
RANDOM()retorna um valor pseudoaleatorio de ponto flutuante em[0, 1)e esta por tras de quase toda consulta "aleatoria" que voce escreve: escolher um usuario de amostra, distribuir grupos A/B ou embaralhar linhas. Vamos ver como usa-lo bem e onde ele vira um gargalo.O valor base e linhas aleatorias
No PostgreSQL,
RANDOM()gera a cada chamada um numero novo de 0 (incluso) ate, mas sem incluir, 1:SELECT random(); -- e.g. 0.8473920192 SELECT random() AS r1, random() AS r2; -- two different valuesA forma classica de pegar linhas aleatorias e ordenar por
RANDOM()e tomar o topo:SELECT id, email, country FROM users ORDER BY random() LIMIT 5;Cada linha recebe uma chave de ordenacao aleatoria, entao o resultado muda a cada execucao. Em tabelas pequenas isso fica legivel e responde na hora.
Por que ORDER BY RANDOM() e custoso
O pulo do gato e o custo. Para ordenar por
RANDOM(), o motor precisa atribuir um numero aleatorio a cada linha e ordenar a tabela inteira antes de aplicar oLIMIT. Em milhoes de linhas isso e um scan completo mais uma ordenacao em disco.LIMIT.Para tabelas grandes, use
TABLESAMPLE, que le paginas aleatorias em vez de ordenar tudo:-- roughly 5% of physical pages, very cheap SELECT id, amount, status FROM orders TABLESAMPLE BERNOULLI (5); -- block sampling: even faster, coarser SELECT * FROM orders TABLESAMPLE SYSTEM (1);Atencao:
TABLESAMPLEdevolve uma porcentagem aproximada, nao um numero exato de linhas, e amostra no nivel de pagina, entao a distribuicao nao e perfeitamente uniforme. Quando voce precisa de exatamente N linhas, combine os dois: amostre de forma grosseira e depois refine.SELECT id, email FROM users TABLESAMPLE SYSTEM (10) ORDER BY random() LIMIT 5;Inteiros aleatorios
RANDOM()da uma fracao, mas muitas vezes voce precisa de umintnum intervalo. A formula efloor(random() * n)para[0, n):-- integer in [0, 100) SELECT floor(random() * 100)::int AS n; -- integer in [1, 6] like a dice roll SELECT floor(random() * 6)::int + 1 AS dice;Um uso pratico e espalhar funcionarios em baldes aleatorios ou atribuir um desconto aleatorio:
SELECT id, name, dept, floor(random() * 3)::int AS shard FROM employees;Atencao: nao escreva
round(random() * n)para um inteiro uniforme.roundpuxa para as pontas a metade das vezes, entao as bordas (0 e n) recebem metade da probabilidade. Use semprefloor.Reprodutibilidade com setseed
A aleatoriedade atrapalha nos testes: o resultado muda a cada execucao.
setseed()fixa o gerador para a sessao atual, recebendo um valor em[-1, 1]:SELECT setseed(0.42); SELECT id FROM users ORDER BY random() LIMIT 3; -- same order on replayDepois de
setseed, a sequencia deRANDOM()e deterministica ate o proximosetseedou o fim da sessao, o que e util para testes e demos repetiveis.MySQL e ClickHouse
A sintaxe varia entre os motores:
RAND(), tambem[0, 1). A amostragem eORDER BY RAND() LIMIT 5; nao existeTABLESAMPLE. A seed vai como argumento:RAND(42).rand(), que e umUInt32em[0, 2^32), nao uma fracao. Para[0,1)divida por4294967295.0; para amostrar ha umSELECT ... SAMPLE 0.1nativo.-- MySQL: random rows + reproducible seed SELECT id, email FROM users ORDER BY RAND(42) LIMIT 5; -- ClickHouse: native sampling, no full sort SELECT id, amount FROM orders SAMPLE 0.1;A licao:
ORDER BY RANDOM()e conveniente e correto, mas nao escala. Com dados grandes, migre para amostragem por paginas e reservesetseed/RAND(seed)para uma saida reproduzivel.