¿Cómo solicitar una fila aleatoria en SQL?

510

¿Cómo puedo solicitar una fila aleatoria (o lo más cercana posible al azar) en SQL puro?

sverrejoh
fuente
Yo solía hacer siempre esto en php después de resultados de consulta de SQL ... esto es probablemente mucho más rápido a la transformación, por límite de 1 apéndice de la solución
CheeseConQueso
2
Parece que no hay una solución de "SQL puro" que se ejecute en cada dbms ... hay una solución para cada uno de ellos.
Manu
Versión de rendimiento: stackoverflow.com/questions/4329396/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Respuestas:

735

Vea esta publicación: SQL para seleccionar una fila aleatoria de una tabla de base de datos . Sigue los métodos para hacerlo en MySQL, PostgreSQL, Microsoft SQL Server, IBM DB2 y Oracle (lo siguiente se copia de ese enlace):

Seleccione una fila aleatoria con MySQL:

SELECT column FROM table
ORDER BY RAND()
LIMIT 1

Seleccione una fila aleatoria con PostgreSQL:

SELECT column FROM table
ORDER BY RANDOM()
LIMIT 1

Seleccione una fila aleatoria con Microsoft SQL Server:

SELECT TOP 1 column FROM table
ORDER BY NEWID()

Seleccione una fila aleatoria con IBM DB2

SELECT column, RAND() as IDX 
FROM table 
ORDER BY IDX FETCH FIRST 1 ROWS ONLY

Seleccione un registro aleatorio con Oracle:

SELECT column FROM
( SELECT column FROM table
ORDER BY dbms_random.value )
WHERE rownum = 1
Yaakov Ellis
fuente
30
-1 para confiar order by rand()o equivalentes en todos los dbs: |. También mencionado aquí .
AD7six
20
Hace diez años un tipo dijo que el uso ORDER BY RAND()está mal ...
trejder
ORDER BY NEWID () parece ser notablemente más lento en SQL Server. Mi consulta se ve así: seleccione los mejores 1000 C.CustomerId, CL.LoginName de Customer C internal join LinkedAccount LA en C.CustomerId = LA.CustomerId internal join CustomerLogin CL en C.CustomerId = CL.CustomerId group por C.CustomerId, CL. LoginName con conteo (*)> 1 pedido por NEWID () Al eliminar la línea "ordenar por NEWID ()" se obtienen resultados mucho más rápidos.
Ben Power
3
Para SQLite use la función RANDOM ().
Slam
10
Estas soluciones no escalan. Están O(n)con nser el número de registros en la tabla. Imagina que tienes 1 millón de registros, ¿realmente quieres generar 1 millón de números aleatorios o identificadores únicos? Prefiero usar COUNT()e involucrar eso en una nueva LIMITexpresión con un solo número aleatorio.
Christian Hujer
174

Soluciones como Jeremies:

SELECT * FROM table ORDER BY RAND() LIMIT 1

funcionan, pero necesitan un escaneo secuencial de toda la tabla (porque el valor aleatorio asociado con cada fila debe calcularse, de modo que se pueda determinar el más pequeño), que puede ser bastante lento incluso para tablas de tamaño mediano. Mi recomendación sería utilizar algún tipo de columna numérica indexada (muchas tablas tienen estas como sus claves principales), y luego escribir algo como:

SELECT * FROM table WHERE num_value >= RAND() * 
    ( SELECT MAX (num_value ) FROM table ) 
ORDER BY num_value LIMIT 1

Esto funciona en tiempo logarítmico, independientemente del tamaño de la tabla, si num_valueestá indexado. Una advertencia: esto supone que num_valuese distribuye equitativamente en el rango 0..MAX(num_value). Si su conjunto de datos se desvía fuertemente de esta suposición, obtendrá resultados asimétricos (algunas filas aparecerán con más frecuencia que otras).

Pantera gris
fuente
8
La segunda sugerencia no es al azar. No puede predecir la fila que se elegirá, pero si tuviera que apostar, apostaría en la segunda fila. Y nunca apostaría en la última fila, es menos probable que se elija cualquiera sea la distribución de su num_value y el tamaño de su mesa.
Etienne Racine
1
Sé que, por lo general, las funciones RAND () no son de muy alta calidad, pero aparte de eso, ¿podría explicar por qué la selección no sería aleatoria?
Grey Panther
13
El primero es INCORRECTO en SQL Server. La función RAND () se invoca solo una vez por consulta, no una vez por fila. Por lo tanto, siempre selecciona la primera fila (pruébalo).
Jeff Walker Code Ranger
3
El segundo también supone que se tienen en cuenta todas las filas: es posible que elija una fila que se haya eliminado.
Sam Rueby
3
@ Sam.Rueby En realidad, num_value> = RAND () ... el límite 1 asegura que las filas vacías se omitirán hasta que encuentre una fila existente.
ghord
62

No sé qué tan eficiente es esto, pero lo he usado antes:

SELECT TOP 1 * FROM MyTable ORDER BY newid()

Debido a que los GUID son bastante aleatorios, el orden significa que obtienes una fila aleatoria.

Matt Hamilton
fuente
1
Estoy usando el servidor MS SQL, SELECCIONE EL TOP 1 * DE some_table_name ORDER BY NEWID () funcionó muy bien para mí, ¡gracias por los consejos chicos!
Eso es exactamente lo mismo queORDER BY RAND() LIMIT 1
Ken Bloom, el
66
Esto también es muy específico de la base de datos ya que usa TOP 1y newid().
Gris
12
Esta es una mala idea. Este método no usará un índice a menos que cada columna se indexe individualmente. La tabla con 100 millones de registros podría tomar mucho tiempo para obtener un registro.
Cambie el
1
@Switch y ¿qué solución propondrías?
Akmal Salikhov
31
ORDER BY NEWID()

toma 7.4 milliseconds

WHERE num_value >= RAND() * (SELECT MAX(num_value) FROM table)

toma 0.0065 milliseconds!

Definitivamente iré con este último método.

Neel
fuente
2
La segunda opción no elegirá la última fila. No sé por qué, solo señalándolo.
Voldemort
77
@Voldemort: rand()devuelve un número de punto flotante ndonde 0 < n < 1. Suponiendo que num_valuees un entero, el valor de retorno de rand() * max(num_value)también se convertirá en un entero, lo que truncará cualquier cosa después del punto decimal. Por lo tanto, rand() * max(num_value)será siempre ser inferior max(num_value), por lo que no se seleccionará la última fila.
Ian Kemp
No seré eficiente si mis datos se eliminan con frecuencia; si encuentro un vacío, tendré que volver a ejecutar toda la consulta.
Loic Coenen
1
@IanKemp Pregunta estúpida, entonces ¿por qué no simplemente usar SELECT MAX (num_value) + 1 ?? Como rand (o RANDOM en la mayoría de los casos) devuelve [0,1), obtendrá el rango completo de valores. Además, sí, tienes razón, tengo que arreglar una consulta.
tekHedd
13

No dijiste qué servidor estás usando. En versiones anteriores de SQL Server, puede usar esto:

select top 1 * from mytable order by newid()

En SQL Server 2005 y versiones posteriores, puede usar TABLESAMPLEpara obtener una muestra aleatoria que se pueda repetir:

SELECT FirstName, LastName
FROM Contact 
TABLESAMPLE (1 ROWS) ;
Jon Galloway
fuente
99
MSDN dice que newid () es preferido sobre la muestra de tabla para obtener resultados verdaderamente aleatorios: msdn.microsoft.com/en-us/library/ms189108.aspx
Andrew Hedges
77
@ Andrew Hedges: ORDENAR POR NEWID () es demasiado costoso
Andrei Rînea
10

Para SQL Server

newid () / order by funcionará, pero será muy costoso para grandes conjuntos de resultados porque tiene que generar una identificación para cada fila y luego ordenarlos.

TABLESAMPLE () es bueno desde el punto de vista del rendimiento, pero obtendrá una agrupación de resultados (se devolverán todas las filas de una página).

Para obtener una muestra aleatoria verdadera con mejor rendimiento, la mejor manera es filtrar las filas al azar. Encontré el siguiente ejemplo de código en el artículo de los Libros en pantalla de SQL Server Limitar los conjuntos de resultados mediante TABLESAMPLE :

Si realmente desea una muestra aleatoria de filas individuales, modifique su consulta para filtrar las filas al azar, en lugar de usar TABLESAMPLE. Por ejemplo, la siguiente consulta usa la función NEWID para devolver aproximadamente el uno por ciento de las filas de la tabla Sales.SalesOrderDetail:

SELECT * FROM Sales.SalesOrderDetail
WHERE 0.01 >= CAST(CHECKSUM(NEWID(),SalesOrderID) & 0x7fffffff AS float)
              / CAST (0x7fffffff AS int)

La columna SalesOrderID se incluye en la expresión CHECKSUM para que NEWID () se evalúe una vez por fila para lograr el muestreo por fila. La expresión CAST (CHECKSUM (NEWID (), SalesOrderID) y 0x7fffffff AS float / CAST (0x7fffffff AS int) se evalúa como un valor flotante aleatorio entre 0 y 1.

Cuando se ejecuta contra una tabla con 1,000,000 de filas, aquí están mis resultados:

SET STATISTICS TIME ON
SET STATISTICS IO ON

/* newid()
   rows returned: 10000
   logical reads: 3359
   CPU time: 3312 ms
   elapsed time = 3359 ms
*/
SELECT TOP 1 PERCENT Number
FROM Numbers
ORDER BY newid()

/* TABLESAMPLE
   rows returned: 9269 (varies)
   logical reads: 32
   CPU time: 0 ms
   elapsed time: 5 ms
*/
SELECT Number
FROM Numbers
TABLESAMPLE (1 PERCENT)

/* Filter
   rows returned: 9994 (varies)
   logical reads: 3359
   CPU time: 641 ms
   elapsed time: 627 ms
*/    
SELECT Number
FROM Numbers
WHERE 0.01 >= CAST(CHECKSUM(NEWID(), Number) & 0x7fffffff AS float) 
              / CAST (0x7fffffff AS int)

SET STATISTICS IO OFF
SET STATISTICS TIME OFF

Si puede salirse con la suya usando TABLESAMPLE, obtendrá el mejor rendimiento. De lo contrario, use el método newid () / filter. newid () / order by debería ser el último recurso si tiene un gran conjunto de resultados.

Rob Boek
fuente
4

Si es posible, use declaraciones almacenadas para evitar la ineficiencia de ambos índices en RND () y crear un campo de número de registro.

PREPARE RandomRecord FROM "SELECT * FROM table LIMIT?, 1";
SET @ n = FLOOR (RAND () * (SELECCIONE EL CONTEO (*) DE la tabla));
EJECUTAR RandomRecord USANDO @n;
ldrut
fuente
Esta solución también se encarga de devolver filas aleatorias cuando el valor numérico indexado utilizado en la cláusula where anterior no se distribuye por igual; así que incluso si toma casi el mismo tiempo (constante) que usar where id_value> = RAND () * MAX (id_value), es mejor.
guido
Por lo que puedo decir, esto no se ejecuta en tiempo constante, se ejecuta en tiempo lineal. En el peor de los casos, @n es igual al número de filas en la tabla y "SELECT * FROM table LIMIT?, 1" evalúa @n - 1 filas hasta llegar a la última.
Andres Riofrio
3

La mejor manera es poner un valor aleatorio en una nueva columna solo para ese propósito, y usar algo como esto (código pseude + SQL):

randomNo = random()
execSql("SELECT TOP 1 * FROM MyTable WHERE MyTable.Randomness > $randomNo")

Esta es la solución empleada por el código MediaWiki. Por supuesto, hay un sesgo en contra de los valores más pequeños, pero descubrieron que era suficiente ajustar el valor aleatorio a cero cuando no se obtienen filas.

La solución newid () puede requerir un escaneo completo de la tabla para que a cada fila se le pueda asignar un nuevo guid, que tendrá mucho menos rendimiento.

La solución rand () puede no funcionar en absoluto (es decir, con MSSQL) porque la función se evaluará solo una vez, y a cada fila se le asignará el mismo número "aleatorio".

Ismaeel
fuente
1
Cuando se obtiene 0 resultados, se obtiene una muestra aleatoria demostrable (no solo "lo suficientemente buena"). Esta solución casi se escala a consultas de varias filas (piense en "mezcla aleatoria"). El problema es que los resultados tienden a seleccionarse en los mismos grupos repetidamente. Para evitar esto, necesitaría redistribuir los números aleatorios que acaba de usar. Puede hacer trampa haciendo un seguimiento de randomNo y configurándolo en max (aleatoriedad) de los resultados, pero luego p (fila i en la consulta 1 Y fila i en la consulta 2) == 0, lo cual no es justo. Déjame hacer algunos cálculos, y te responderé con un esquema verdaderamente justo.
alsuren
3

Para SQL Server 2005 y 2008, si queremos una muestra aleatoria de filas individuales (de Books Online ):

SELECT * FROM Sales.SalesOrderDetail
WHERE 0.01 >= CAST(CHECKSUM(NEWID(), SalesOrderID) & 0x7fffffff AS float)
/ CAST (0x7fffffff AS int)
Santiago Cepas
fuente
3

En lugar de usar RAND (), como no se recomienda , simplemente puede obtener la ID máxima (= Max):

SELECT MAX(ID) FROM TABLE;

obtener un azar entre 1..Max (= My_Generated_Random)

My_Generated_Random = rand_in_your_programming_lang_function(1..Max);

y luego ejecuta este SQL:

SELECT ID FROM TABLE WHERE ID >= My_Generated_Random ORDER BY ID LIMIT 1

Tenga en cuenta que comprobará si hay filas cuyos ID sean IGUALES o SUPERIORES al valor elegido. También es posible buscar la fila hacia abajo en la tabla y obtener una ID igual o inferior que My_Generated_Random, luego modificar la consulta de esta manera:

SELECT ID FROM TABLE WHERE ID <= My_Generated_Random ORDER BY ID DESC LIMIT 1
forsberg
fuente
¿Qué pasaría si la ID aleatoria generada ya no existe en la tabla? Las filas eliminadas o pasivas que no desea mostrar al usuario podrían causar problemas.
Ebleme
Nada. Obtiene el número de identificación MÁS CERCANO, no exacto. Si considera que id = 1 se eliminará, intercambie 1 con un mínimo.
forsberg
2

Como se señaló en el comentario de @ BillKarwin sobre la respuesta de @ cnu ...

Al combinar con un LIMIT, descubrí que funciona mucho mejor (al menos con PostgreSQL 9.1) para UNIRSE con un orden aleatorio en lugar de ordenar directamente las filas reales: por ejemplo

SELECT * FROM tbl_post AS t
JOIN ...
JOIN ( SELECT id, CAST(-2147483648 * RANDOM() AS integer) AS rand
       FROM tbl_post
       WHERE create_time >= 1349928000
     ) r ON r.id = t.id
WHERE create_time >= 1349928000 AND ...
ORDER BY r.rand
LIMIT 100

Solo asegúrese de que la 'r' genere un valor 'rand' para cada valor clave posible en la consulta compleja que se une con ella, pero aún limite el número de filas de 'r' cuando sea posible.

CAST as Integer es especialmente útil para PostgreSQL 9.2 que tiene una optimización de clasificación específica para tipos flotantes de precisión entera y única.

karmakaze
fuente
1

La mayoría de las soluciones aquí apuntan a evitar la clasificación, pero aún necesitan hacer un escaneo secuencial sobre una tabla.

También hay una manera de evitar el escaneo secuencial cambiando al escaneo de índice. Si conoce el valor del índice de su fila aleatoria, puede obtener el resultado casi de forma instantánea. El problema es cómo adivinar un valor de índice.

La siguiente solución funciona en PostgreSQL 8.4:

explain analyze select * from cms_refs where rec_id in 
  (select (random()*(select last_value from cms_refs_rec_id_seq))::bigint 
   from generate_series(1,10))
  limit 1;

En la solución anterior, adivina 10 diferentes valores de índice aleatorio del rango 0 .. [último valor de id].

El número 10 es arbitrario: puede usar 100 o 1000 ya que (sorprendentemente) no tiene un gran impacto en el tiempo de respuesta.

También hay un problema: si tiene identificadores escasos , puede pasar por alto . La solución es tener un plan de respaldo :) En este caso, un orden antiguo puro por consulta aleatoria (). Cuando la identificación combinada se ve así:

explain analyze select * from cms_refs where rec_id in 
    (select (random()*(select last_value from cms_refs_rec_id_seq))::bigint 
     from generate_series(1,10))
    union all (select * from cms_refs order by random() limit 1)
    limit 1;

No es la unión TODA cláusula. En este caso, si la primera parte devuelve datos, la segunda NUNCA se ejecuta.

hegemon
fuente
1

Últimamente, pero llegué aquí a través de Google, por lo que, en aras de la posteridad, agregaré una solución alternativa.

Otro enfoque es usar TOP dos veces, con órdenes alternas. No sé si es "SQL puro", porque usa una variable en el TOP, pero funciona en SQL Server 2008. Aquí hay un ejemplo que uso en una tabla de palabras del diccionario, si quiero una palabra al azar.

SELECT TOP 1
  word
FROM (
  SELECT TOP(@idx)
    word 
  FROM
    dbo.DictionaryAbridged WITH(NOLOCK)
  ORDER BY
    word DESC
) AS D
ORDER BY
  word ASC

Por supuesto, @idx es un número entero generado aleatoriamente que varía de 1 a COUNT (*) en la tabla de destino, inclusive. Si su columna está indexada, también se beneficiará de ella. Otra ventaja es que puede usarlo en una función, ya que NEWID () no está permitido.

Por último, la consulta anterior se ejecuta en aproximadamente 1/10 del tiempo de ejecución de un tipo de consulta NEWID () en la misma tabla. YYMV

alphadogg
fuente
1

También puede intentar usar la new id()función.

Simplemente escriba su consulta y use el orden por new id()función. Es bastante al azar.

Jai - gotaninterviewcall
fuente
1

Para que MySQL obtenga un registro aleatorio

 SELECT name
  FROM random AS r1 JOIN
       (SELECT (RAND() *
                     (SELECT MAX(id)
                        FROM random)) AS id)
        AS r2
 WHERE r1.id >= r2.id
 ORDER BY r1.id ASC
 LIMIT 1

Más detalles http://jan.kneschke.de/projects/mysql/order-by-rand/

Sophy
fuente
Después de probar muchas de las respuestas, creo que esta es la mejor. Parece ser rápido y elige un buen número aleatorio cada vez. Parece similar a la segunda sugerencia de @GreyPanther anterior, pero esta respuesta elige más números aleatorios.
Jeff Baker,
1

Todavía no vi esta variación en las respuestas. Tenía una restricción adicional donde necesitaba, dada una semilla inicial, para seleccionar el mismo conjunto de filas cada vez.

Para MS SQL:

Ejemplo mínimo:

select top 10 percent *
from table_name
order by rand(checksum(*))

Tiempo de ejecución normalizado: 1.00

Ejemplo de NewId ():

select top 10 percent *
from table_name
order by newid()

Tiempo de ejecución normalizado: 1.02

NewId()es insignificantemente más lento que rand(checksum(*)), por lo que es posible que no desee utilizarlo contra grandes conjuntos de registros.

Selección con semilla inicial:

declare @seed int
set @seed = Year(getdate()) * month(getdate()) /* any other initial seed here */

select top 10 percent *
from table_name
order by rand(checksum(*) % seed) /* any other math function here */

Si necesita seleccionar el mismo conjunto dado una semilla, esto parece funcionar.

klyd
fuente
1

En MSSQL (probado en 11.0.5569) usando

SELECT TOP 100 * FROM employee ORDER BY CRYPT_GEN_RANDOM(10)

es significativamente más rápido que

SELECT TOP 100 * FROM employee ORDER BY NEWID()
David Knight
fuente
1

En SQL Server puede combinar TABLESAMPLE con NEWID () para obtener una aleatoriedad bastante buena y aún así tener velocidad. Esto es especialmente útil si realmente solo desea 1 o un pequeño número de filas.

SELECT TOP 1 * FROM [table] 
TABLESAMPLE (500 ROWS) 
ORDER BY NEWID()
Chris Arbogast
fuente
1

Con SQL Server 2012+ puede usar la consulta OFFSET FETCH para hacer esto para una sola fila aleatoria

select  * from MyTable ORDER BY id OFFSET n ROW FETCH NEXT 1 ROWS ONLY

donde id es una columna de identidad y n es la fila que desea, calculada como un número aleatorio entre 0 y count () - 1 de la tabla (el desplazamiento 0 es la primera fila después de todo)

Esto funciona con agujeros en los datos de la tabla, siempre que tenga un índice para trabajar para la cláusula ORDER BY. También es muy bueno para la aleatoriedad, ya que te esfuerzas para pasar, pero los inconvenientes en otros métodos no están presentes. Además, el rendimiento es bastante bueno, en un conjunto de datos más pequeño se mantiene bien, aunque no he probado pruebas de rendimiento serias en varios millones de filas.

gbjbaanb
fuente
0
 SELECT * FROM table ORDER BY RAND() LIMIT 1
Jeremy Ruten
fuente
Hace diez años (2005), un tipo dijo que usar ORDER BY RAND()está mal ...
trejder
0

Tengo que estar de acuerdo con CD-MaN: Usar "ORDER BY RAND ()" funcionará bien para tablas pequeñas o cuando haga su SELECCIÓN solo unas pocas veces.

También uso la técnica "num_value> = RAND () * ...", y si realmente quiero tener resultados aleatorios, tengo una columna especial "aleatoria" en la tabla que actualizo una vez al día más o menos. Esa única ejecución de ACTUALIZACIÓN llevará algún tiempo (especialmente porque tendrá que tener un índice en esa columna), pero es mucho más rápido que crear números aleatorios para cada fila cada vez que se ejecuta la selección.

BlaM
fuente
0

Tenga cuidado porque TableSample en realidad no devuelve una muestra aleatoria de filas. Dirige su consulta para mirar una muestra aleatoria de las páginas de 8 KB que forman su fila. Luego, su consulta se ejecuta contra los datos contenidos en estas páginas. Debido a cómo se pueden agrupar los datos en estas páginas (orden de inserción, etc.), esto podría generar datos que en realidad no son una muestra aleatoria.

Ver: http://www.mssqltips.com/tip.asp?tip=1308

Esta página de MSDN para TableSample incluye un ejemplo de cómo generar una muestra de datos realmente aleatoria.

http://msdn.microsoft.com/en-us/library/ms189108.aspx

Sean Turner
fuente
0

Parece que muchas de las ideas enumeradas todavía usan el orden

Sin embargo, si usa una tabla temporal, puede asignar un índice aleatorio (como lo han sugerido muchas de las soluciones), y luego tomar la primera que sea mayor que un número arbitrario entre 0 y 1.

Por ejemplo (para DB2):

WITH TEMP AS (
SELECT COMLUMN, RAND() AS IDX FROM TABLE)
SELECT COLUMN FROM TABLE WHERE IDX > .5
FETCH FIRST 1 ROW ONLY
DAVID
fuente
2
Después de considerar esta solución, he encontrado una falla fundamental en mi lógica. Esto devolvería consistentemente los mismos valores de configuración pequeños, cerca del comienzo de la tabla, porque supongo que si hubo una distribución uniforme entre 0 y 1, existe una probabilidad del 50% de que la primera fila cumpla con ese criterio.
DAVID
0

Hay una mejor solución para Oracle en lugar de usar dbms_random.value, mientras que requiere un escaneo completo para ordenar filas por dbms_random.value y es bastante lento para tablas grandes.

Use esto en su lugar:

SELECT *
FROM employee sample(1)
WHERE rownum=1
sev3ryn
fuente
0

Para Firebird:

Select FIRST 1 column from table ORDER BY RAND()
Luigi04
fuente
0

Para SQL Server 2005 y superior, extender la respuesta de @ GreyPanther para los casos en que num_valueno tiene valores continuos. Esto también funciona para los casos en que no hemos distribuido uniformemente los conjuntos de datos y cuando num_valueno es un número sino un identificador único.

WITH CTE_Table (SelRow, num_value) 
AS 
(
    SELECT ROW_NUMBER() OVER(ORDER BY ID) AS SelRow, num_value FROM table
) 

SELECT * FROM table Where num_value = ( 
    SELECT TOP 1 num_value FROM CTE_Table  WHERE SelRow >= RAND() * (SELECT MAX(SelRow) FROM CTE_Table)
)
Endri
fuente
-1

La función aleatoria del sql podría ayudar. Además, si desea limitar a una sola fila, simplemente agréguela al final.

SELECT column FROM table
ORDER BY RAND()
LIMIT 1
nvnvashisth
fuente