2011-03-14 14 views
74

Tengo una tabla en postgres que contiene un par de millones de filas. He comprobado en el Internet y me encontré con la siguienteselección de fila aleatoria rápida en Postgres

SELECT myid FROM mytable ORDER BY RANDOM() LIMIT 1; 

funciona, pero es muy lento ... ¿hay otra manera de hacer esa consulta, o una manera directa para seleccionar una fila al azar sin leer toda la ¿mesa? por cierto, 'myid' es un número entero, pero puede ser un campo vacío.

gracias

Respuesta

83

Es posible que desee experimentar con OFFSET, como en

SELECT myid FROM mytable OFFSET floor(random()*N) LIMIT 1;

El N es el número de filas en mytable. Es posible que primero necesite hacer un SELECT COUNT(*) para calcular el valor de N.

actualización (por Antony Hatchkins)

Debe utilizar floor aquí:

SELECT myid FROM mytable OFFSET floor(random()*N) LIMIT 1; 

considere una tabla de 2 filas; random()*N genera 0 <= x < 2 y por ejemplo SELECT myid FROM mytable OFFSET 1.7 LIMIT 1; devuelve 0 filas debido al redondeo implícito al int más cercano.

+0

hacerla sentido utilizar un N menos de 'SELECT COUNT (*)' ?, quiero decir , no usa todos los valores en la tabla sino solo una parte de ellos? – Juan

+0

@Juan Eso depende de sus requisitos. – NPE

+0

utilizando el 'EXPLAIN SELECT ...' con diferentes valores de N dan el mismo costo para la consulta, entonces supongo que es mejor ir por el valor máximo de N. – Juan

13

Revise este enlace para ver algunas opciones diferentes. http://www.depesz.com/index.php/2007/09/16/my-thoughts-on-getting-random-row/

Actualización: (A.Hatchkins)

El resumen del artículo (muy) largo es el siguiente.

El autor enumera cuatro enfoques:

1) ORDER BY random() LIMIT 1; - lentos

2) ORDER BY id where id>=random()*N LIMIT 1 - no uniforme si hay lagunas

3) la columna al azar - debe actualizarse cada ahora y entonces

4) de encargo random aggregate - astucia método, podría ser lento: al azar() necesita ser generada N veces

y sugiere mejorar el método # 2 usando

5) ORDER BY id where id=random()*N LIMIT 1 con posteriores consultas si el resultado está vacío.

+0

Me pregunto por qué no cubrieron OFFSET? Usar una ORDEN está fuera de cuestión solo para obtener una fila al azar. Afortunadamente, OFFSET está bien cubierto en las respuestas. – user3175580

+0

no estoy seguro de por qué la columna aleatoria alguna vez necesitaría ser actualizada ... – rogerdpack

32

Intenté esto con una subconsulta y funcionó bien. Offset, al menos en Postgresql v8.4.4 funciona bien.

select * from mytable offset random() * (select count(*) from mytable) limit 1 ; 
+0

De hecho, v8.4 es esencial para que esto funcione, no funciona para <= 8.3. –

+1

ver una corrección de error en mi respuesta a continuación –

26

Es necesario utilizar floor:

SELECT myid FROM mytable OFFSET floor(random()*N) LIMIT 1; 
+0

Considere una tabla de 2 filas; 'random() * N' genera 0 <= x <2 y por ejemplo' SELECT myid FROM mytable OFFSET 1.7 LIMIT 1; 'devuelve 0 filas debido al redondeo implícito al int más cercano. –

+0

Desafortunadamente, esto no funciona si desea usar un LÍMITE más alto ... Necesito obtener 3 elementos, así que necesito usar la sintaxis ORDER BY RANDOM(). –

+1

Tres consultas consecutivas seguirán siendo más rápidas que una 'orden aleatoria()', aproximadamente como '3 * O (N)

31

PostgreSQL 9.5 introdujo un nuevo enfoque para la selección de la muestra mucho más rápido: TABLESAMPLE

La sintaxis es

SELECT * FROM my_table TABLESAMPLE BERNOULLI(percentage); 
SELECT * FROM my_table TABLESAMPLE SYSTEM(percentage); 

Ésta no es la solución óptima si desea sólo una fila seleccionada, porque lo que necesita saber el Conde de la tabla para calcular el porcentaje exacto.

Para evitar un conteo lento y utilizar TABLESAMPLE rápido para tablas de 1 fila a los mil millones de filas, puede hacerlo:

SELECT * FROM my_table TABLESAMPLE SYSTEM(0.000001) LIMIT 1; 
if you got no result: 
SELECT * FROM my_table TABLESAMPLE SYSTEM(0.00001) LIMIT 1; 
if you got no result: 
SELECT * FROM my_table TABLESAMPLE SYSTEM(0.0001) LIMIT 1; 
if you got no result: 
SELECT * FROM my_table TABLESAMPLE SYSTEM(0.001) LIMIT 1; 
... 

Esto puede no parecer tan elegante, pero probablemente es más rápido que cualquiera de los otros respuestas

para decidir si desea utilizar el sistema de Bernulli oder, leer acerca de la diferencia en http://blog.2ndquadrant.com/tablesample-in-postgresql-9-5-2/

+1

Esto es mucho más rápido y más fácil que cualquier otra respuesta, este debería ser el primero. –

2

He ocurrió una solución muy rápida y sin TABLESAMPLE. Mucho más rápido que OFFSET random()*N LIMIT 1. Ni siquiera requiere contar la mesa.

La idea es crear un índice de expresión con datos aleatorios pero predecibles, por ejemplo md5(primary key).

Aquí está una prueba con 1M de datos filas de ejemplo:

create table randtest (id serial primary key, data int not null); 

insert into randtest (data) select (random()*1000000)::int from generate_series(1,1000000); 

create index randtest_md5_id_idx on randtest (md5(id::text)); 

explain analyze 
select * from randtest where md5(id::text)>md5(random()::text) 
order by md5(id::text) limit 1; 

Resultado:

Limit (cost=0.42..0.68 rows=1 width=8) (actual time=6.219..6.220 rows=1 loops=1) 
    -> Index Scan using randtest_md5_id_idx on randtest (cost=0.42..84040.42 rows=333333 width=8) (actual time=6.217..6.217 rows=1 loops=1) 
     Filter: (md5((id)::text) > md5((random())::text)) 
     Rows Removed by Filter: 1831 
Total runtime: 6.245 ms 

Esta consulta puede a veces (con cerca de 1/NUMBER_OF_ROWS probabilidad) de retorno 0 filas, por lo que debe ser revisado y volver a ejecutar. Además, las probabilidades no son exactamente las mismas: algunas filas son más probables que otras.

Para la comparación:

explain analyze SELECT id FROM randtest OFFSET random()*1000000 LIMIT 1; 

resultados varían ampliamente, pero puede ser bastante malo:

Limit (cost=1442.50..1442.51 rows=1 width=4) (actual time=179.183..179.184 rows=1 loops=1) 
    -> Seq Scan on randtest (cost=0.00..14425.00 rows=1000000 width=4) (actual time=0.016..134.835 rows=915702 loops=1) 
Total runtime: 179.211 ms 
(3 rows) 
+0

Rápido, sí. Verdaderamente al azar, no. Un md5 valores que pasa a ser el siguiente mayor valor después de otro valor existente tiene una pequeña posibilidad de ser elegido, mientras que los valores después de una gran brecha en el espacio de números tienen una probabilidad mucho mayor (mayor por el número de valores posibles en el medio) . La distribución resultante no es aleatoria. –

+0

muy interesante, podría funcionar en un caso de uso de una consulta tipo lotería: la consulta debe examinar todos los tickets disponibles y devolver aleatoriamente UN solo ticket. también puedo usar un bloqueo pesimista (seleccione ... para actualizar) con su técnica? – Mathieu

+0

Para cualquier cosa relacionada con la lotería, debería usar un muestreo aleatorio justo y criptográficamente seguro; por ejemplo, elija un número aleatorio entre 1 y máximo (id) hasta encontrar la identificación existente. El método de esta respuesta no es justo ni seguro, es rápido. Se puede usar para cosas como 'obtener al azar 1% de filas para probar algo' o 'mostrar 5 entradas aleatorias'. – Tometzky

Cuestiones relacionadas