¿Cómo puedo solicitar una fila aleatoria (o lo más cercana posible al azar) en SQL puro?
¿Cómo puedo solicitar una fila aleatoria (o lo más cercana posible al azar) en SQL puro?
Respuestas:
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
order by rand()
o equivalentes en todos los dbs: |. También mencionado aquí .
ORDER BY RAND()
está mal ...
O(n)
con n
ser 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 LIMIT
expresión con un solo número aleatorio.
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_value
está indexado. Una advertencia: esto supone que num_value
se 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).
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.
ORDER BY RAND() LIMIT 1
TOP 1
y newid()
.
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.
rand()
devuelve un número de punto flotante n
donde 0 < n < 1
. Suponiendo que num_value
es 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.
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 TABLESAMPLE
para obtener una muestra aleatoria que se pueda repetir:
SELECT FirstName, LastName
FROM Contact
TABLESAMPLE (1 ROWS) ;
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.
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;
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".
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)
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
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.
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.
Ú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
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.
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/
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.
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()
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()
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.
SELECT * FROM table ORDER BY RAND() LIMIT 1
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.
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.
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
Una manera simple y eficiente de http://akinas.com/pages/en/blog/mysql_random_row/
SET @i = (SELECT FLOOR(RAND() * COUNT(*)) FROM table); PREPARE get_stmt FROM 'SELECT * FROM table LIMIT ?, 1'; EXECUTE get_stmt USING @i;
Para SQL Server 2005 y superior, extender la respuesta de @ GreyPanther para los casos en que num_value
no tiene valores continuos. Esto también funciona para los casos en que no hemos distribuido uniformemente los conjuntos de datos y cuando num_value
no 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)
)
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