sqlpostgresqlrandomsampling

RANDOM() en SQL: muestreo aleatorio, enteros y semillas

Como funciona RANDOM() en PostgreSQL, por que ORDER BY RANDOM() es lento a gran escala y con que sustituirlo.

2 min de lecturaReferencesql · postgresql · random · sampling · mysql

RANDOM() devuelve un valor pseudoaleatorio en coma flotante dentro de [0, 1) y esta detras de casi cualquier consulta "aleatoria": elegir un usuario de muestra, repartir grupos A/B o barajar filas. Veamos como usarlo bien y donde se convierte en un cuello de botella.

El valor base y filas aleatorias

En PostgreSQL, RANDOM() produce cada vez un numero nuevo entre 0 (incluido) y 1 (sin incluir):

SELECT random();              -- e.g. 0.8473920192
SELECT random() AS r1, random() AS r2;  -- two different values

La forma clasica de sacar filas al azar es ordenar por RANDOM() y tomar la parte superior:

SELECT id, email, country
FROM users
ORDER BY random()
LIMIT 5;

Cada fila recibe una clave de orden aleatoria, asi que el resultado cambia en cada ejecucion. En tablas pequenas se lee con claridad y responde al instante.

Por que ORDER BY RANDOM() es caro

El detalle esta en el coste. Para ordenar por RANDOM(), el motor debe asignar un numero aleatorio a cada fila y ordenar la tabla entera antes de aplicar el LIMIT. Con millones de filas eso es un escaneo completo mas un ordenamiento en disco.

  • Un indice sobre la expresion no ayuda: la clave cambia en cada llamada.
  • El coste crece con el tamano de la tabla, no con el LIMIT.

Para tablas grandes usa TABLESAMPLE, que lee paginas al azar en vez de ordenarlo todo:

-- 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);

Cuidado: TABLESAMPLE devuelve un porcentaje aproximado, no un numero exacto de filas, y muestrea a nivel de pagina, asi que la distribucion no es perfectamente uniforme. Cuando necesitas exactamente N filas, combina ambos: muestrea de forma gruesa y luego afina.

SELECT id, email
FROM users TABLESAMPLE SYSTEM (10)
ORDER BY random()
LIMIT 5;

Enteros aleatorios

RANDOM() da una fraccion, pero a menudo necesitas un int en un rango. La formula es floor(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;

Un uso practico es repartir empleados en grupos al azar o asignar un descuento aleatorio:

SELECT id, name, dept,
       floor(random() * 3)::int AS shard
FROM employees;

Cuidado: no escribas round(random() * n) para un entero uniforme. round tira hacia los extremos la mitad de veces, asi que los bordes (0 y n) reciben la mitad de probabilidad. Usa siempre floor.

Reproducibilidad con setseed

La aleatoriedad incomoda en las pruebas: el resultado cambia en cada ejecucion. setseed() fija el generador para la sesion actual, tomando un valor en [-1, 1]:

SELECT setseed(0.42);
SELECT id FROM users ORDER BY random() LIMIT 3;  -- same order on replay

Tras setseed, la secuencia de RANDOM() es determinista hasta el siguiente setseed o el fin de la sesion, lo cual es comodo para pruebas y demos repetibles.

MySQL y ClickHouse

La sintaxis difiere entre motores:

  • MySQL usa RAND(), tambien [0, 1). El muestreo es ORDER BY RAND() LIMIT 5; no existe TABLESAMPLE. La semilla va como argumento: RAND(42).
  • ClickHouse tiene rand(), que es un UInt32 en [0, 2^32), no una fraccion. Para [0,1) divide por 4294967295.0; para muestrear ofrece un SELECT ... SAMPLE 0.1 nativo.
-- 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;

La conclusion: ORDER BY RANDOM() es comodo y correcto pero no escala. Con datos grandes pasa al muestreo por paginas y reserva setseed/RAND(seed) para una salida reproducible.

Practica con ejercicios reales

Resuelve ejercicios en el entrenador de SQL con corrección instantánea y pistas.

Abrir el entrenador