MySQL selecciona 10 filas aleatorias de 600K filas rápidamente


463

¿Cómo puedo escribir mejor una consulta que seleccione 10 filas al azar de un total de 600k?


15
Aquí hay 8 técnicas ; quizás uno funcione bien en su caso.
Rick James

Respuestas:


386

Una gran publicación manejando varios casos, desde simples, hasta huecos, hasta no uniformes con huecos.

http://jan.kneschke.de/projects/mysql/order-by-rand/

Para el caso más general, así es como lo hace:

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

Esto supone que la distribución de identificadores es igual y que puede haber huecos en la lista de identificadores. Vea el artículo para ejemplos más avanzados.


52
Sí, si tiene brechas potencialmente grandes en las identificaciones, entonces la posibilidad de que sus identificaciones más bajas se elijan al azar es mucho menor que sus identificaciones altas. De hecho, la posibilidad de que la primera identificación después de la brecha más grande sea realmente la más alta. Por lo tanto, esto no es aleatorio por definición.
lukeocodes

66
¿Cómo se obtienen 10 filas aleatorias diferentes? ¿Tiene que establecer el límite en 10 y luego iterar 10 veces con mysqli_fetch_assoc($result)? ¿O esos 10 resultados no son necesariamente distinguibles?
Adam

12
Aleatorio requiere una oportunidad igual para cualquier resultado, en mi opinión. ;)
lukeocodes

44
El artículo completo aborda cuestiones como distribuciones desiguales y resultados repetidos.
Bradd Szonye

1
específicamente, si tiene un espacio al comienzo de sus ID, se elegirá el primero (min / max-min) de la hora. Para ese caso, un simple ajuste es MAX () - MIN () * RAND + MIN (), que no es demasiado lento.
Code Abominator

343
SELECT column FROM table
ORDER BY RAND()
LIMIT 10

No es la solución eficiente pero funciona


139
ORDER BY RAND()es relativamente lento
Mateusz Charytoniuk

77
Mateusz - prueba pls, SELECT words, transcription, translation, sound FROM vocabulary WHERE menu_id=$menuId ORDER BY RAND() LIMIT 10toma 0.0010, sin LIMIT 10 tomó 0.0012 (en esa tabla 3500 palabras).
Arthur Kushman

26
@zeusakm 3500 palabras no es tanto; el problema es que explota más allá de cierto punto porque MySQL tiene que ordenar TODOS los registros después de leer cada uno; Una vez que la operación llega al disco duro, puede sentir la diferencia.
Ja͢ck

16
No quiero repetirme, pero de nuevo, es un escaneo completo de la tabla. En una mesa grande consume mucho tiempo y memoria, y puede causar la creación y operación de una tabla temporal en el disco, lo cual es muy lento.
mate

10
Cuando estaba entrevistando con Facebook en 2010, me preguntaron cómo seleccionar un registro aleatorio de un archivo enorme de tamaño desconocido, en una sola lectura. Una vez que se le ocurre una idea, es fácil generalizarla para seleccionar múltiples registros. Entonces sí, ordenar todo el archivo es ridículo. Al mismo tiempo, es muy útil. Acabo de utilizar este enfoque para elegir 10 filas al azar de una tabla con más de 1,000,000 de filas. Claro, tuve que esperar un poco; pero yo sólo quería tener una idea, lo típico filas en esta tabla se parece ...
osa

27

Consulta simple que tiene un excelente rendimiento y funciona con lagunas :

SELECT * FROM tbl AS t1 JOIN (SELECT id FROM tbl ORDER BY RAND() LIMIT 10) as t2 ON t1.id=t2.id

Esta consulta en una tabla de 200K toma 0.08s y la versión normal (SELECT * FROM tbl ORDER BY RAND () LIMIT 10) toma 0.35s en mi máquina.

Esto es rápido porque la fase de clasificación solo usa la columna de ID indexada. Puede ver este comportamiento en la explicación:

SELECCIONAR * DESDE tbl ORDENAR POR RAND () LÍMITE 10: Explicación simple

SELECT * FROM tbl AS t1 JOIN (SELECT id FROM tbl ORDER BY RAND () LIMIT 10) as t2 ON t1.id = t2.id ingrese la descripción de la imagen aquí

Versión ponderada : https://stackoverflow.com/a/41577458/893432


1
Lo siento, lo probé! Rendimiento lento en 600k registros.
Dylan B

@DylanB Actualicé la respuesta con una prueba.
Ali

17

Recibo consultas rápidas (alrededor de 0,5 segundos) con una CPU lenta , seleccionando 10 filas aleatorias en una base de datos de MySQL de 400 K de tamaño no almacenado en caché de 2 Gb. Vea aquí mi código: Selección rápida de filas aleatorias en MySQL

<?php
$time= microtime_float();

$sql='SELECT COUNT(*) FROM pages';
$rquery= BD_Ejecutar($sql);
list($num_records)=mysql_fetch_row($rquery);
mysql_free_result($rquery);

$sql="SELECT id FROM pages WHERE RAND()*$num_records<20
   ORDER BY RAND() LIMIT 0,10";
$rquery= BD_Ejecutar($sql);
while(list($id)=mysql_fetch_row($rquery)){
    if($id_in) $id_in.=",$id";
    else $id_in="$id";
}
mysql_free_result($rquery);

$sql="SELECT id,url FROM pages WHERE id IN($id_in)";
$rquery= BD_Ejecutar($sql);
while(list($id,$url)=mysql_fetch_row($rquery)){
    logger("$id, $url",1);
}
mysql_free_result($rquery);

$time= microtime_float()-$time;

logger("num_records=$num_records",1);
logger("$id_in",1);
logger("Time elapsed: <b>$time segundos</b>",1);
?>

11
Teniendo en cuenta mi mesa de más de 14 millones de discos, esto es lo más lentoORDER BY RAND()
Fabricio

55
@snippetsofcode En su caso: 400k de filas puede usar el simple "ORDER BY rand ()". Tu truco con 3 consultas es inútil. Puede reescribirlo como "SELECCIONAR ID, url DE páginas DONDE ID (SELECCIONAR ID de páginas ORDENAR POR rand () LÍMITE 10)"
Roman Podlinov

44
Su técnica todavía hace un escaneo de tabla. Use FLUSH STATUS; SELECT ...; SHOW SESSION STATUS LIKE 'Handler%';para verlo.
Rick James

44
También intente ejecutar esa consulta en la página web 200 req / s. La concurrencia te matará.
Marki555

El beneficio de @RomanPodlinov de esto sobre plano ORDER BY RAND()es que clasifica solo los identificadores (no las filas completas), por lo que la tabla temporal es más pequeña, pero aún tiene que ordenarlos todos.
Marki555

16

Su consulta de línea simple y muy simple.

SELECT * FROM Table_Name ORDER BY RAND() LIMIT 0,10;

21
FYI, order by rand()es muy lento si la mesa es grande
evilReiko

66
A veces se acepta SLOW si quiero mantenerlo SIMPLE

La indexación debe aplicarse en la tabla si es grande.
Muhammad Azeem

1
La indexación no ayudará aquí. Los índices son útiles para cosas muy específicas, y esta consulta no es una de ellas.
Andrew

13

Del libro:

Elija una fila aleatoria usando un desplazamiento

Aún otra técnica que evita los problemas encontrados en las alternativas anteriores es contar las filas en el conjunto de datos y devolver un número aleatorio entre 0 y el recuento. Luego use este número como compensación cuando consulte el conjunto de datos

<?php
$rand = "SELECT ROUND(RAND() * (SELECT COUNT(*) FROM Bugs))";
$offset = $pdo->query($rand)->fetch(PDO::FETCH_ASSOC);
$sql = "SELECT * FROM Bugs LIMIT 1 OFFSET :offset";
$stmt = $pdo->prepare($sql);
$stmt->execute( $offset );
$rand_bug = $stmt->fetch();

Utilice esta solución cuando no pueda asumir valores clave contiguos y necesite asegurarse de que cada fila tenga una posibilidad uniforme de ser seleccionada.


1
para mesas muy grandes, se SELECT count(*)vuelve lento.
Hans Z

7

Cómo seleccionar filas aleatorias de una tabla:

Desde aquí: seleccione filas aleatorias en MySQL

Una mejora rápida sobre el "escaneo de tabla" es utilizar el índice para recoger identificadores aleatorios.

SELECT *
FROM random, (
        SELECT id AS sid
        FROM random
        ORDER BY RAND( )
        LIMIT 10
    ) tmp
WHERE random.id = tmp.sid;

1
Eso ayuda a algunos para MyISAM, pero no para InnoDB (suponiendo que id es el agrupado PRIMARY KEY).
Rick James

7

Bueno, si no tiene espacios en blanco en sus teclas y todas son numéricas, puede calcular números aleatorios y seleccionar esas líneas. pero esto probablemente no será el caso.

Entonces, una solución sería la siguiente:

SELECT * FROM table WHERE key >= FLOOR(RAND()*MAX(id)) LIMIT 1

que básicamente garantizará que obtenga un número aleatorio en el rango de sus teclas y luego seleccione el siguiente mejor que sea mayor. Tienes que hacer esto 10 veces.

sin embargo, esto NO es realmente aleatorio porque sus claves probablemente no se distribuirán de manera uniforme.

Es realmente un gran problema y no es fácil de resolver cumpliendo todos los requisitos, el rand () de MySQL es lo mejor que puede obtener si realmente desea 10 filas aleatorias.

Sin embargo, existe otra solución que es rápida, pero también tiene una compensación en lo que respecta a la aleatoriedad, pero puede ser mejor para usted. Lea sobre esto aquí: ¿Cómo puedo optimizar la función ORDER BY RAND () de MySQL?

La pregunta es qué tan aleatorio necesitas que sea.

¿Puedes explicarme un poco más para que pueda darte una buena solución?

Por ejemplo, una empresa con la que trabajé tenía una solución en la que necesitaban una aleatoriedad absoluta extremadamente rápida. Terminaron rellenando previamente la base de datos con valores aleatorios que se seleccionaron descendiendo y luego se establecieron en diferentes valores aleatorios nuevamente.

Si casi nunca actualiza, también puede completar una identificación incremental para que no tenga huecos y solo pueda calcular claves aleatorias antes de seleccionar ... ¡Depende del caso de uso!


Hola Joe. En este caso particular, las claves no deben carecer de espacios, pero con el tiempo esto puede cambiar. Y mientras su respuesta funciona, generará las 10 filas aleatorias (siempre que escriba el límite 10) que son consecutivas y quería más aleatoriedad, por así decirlo. :) Gracias.
Francisc

Si necesita 10, use algún tipo de unión para generar 10 filas únicas.
johno

Tahts lo que dije. necesitas ejecutar eso 10 veces. combinarlo wition union es una forma de ponerlo en una consulta. ver mi apéndice hace 2 minutos.
The Surrican

1
@TheSurrican, esta solución se ve genial pero es muy defectuosa . Intente insertar solo uno muy grande Idy todo sus consultas aleatorias le devolverán esa Id.
Pacerier

1
FLOOR(RAND()*MAX(id))está sesgado hacia la devolución de identificadores más grandes.
Rick James

3

Necesitaba una consulta para devolver una gran cantidad de filas aleatorias de una tabla bastante grande. Esto es lo que se me ocurrió. Primero obtenga el ID de registro máximo:

SELECT MAX(id) FROM table_name;

Luego sustituya ese valor en:

SELECT * FROM table_name WHERE id > FLOOR(RAND() * max) LIMIT n;

Donde max es el ID de registro máximo en la tabla yn es el número de filas que desea en su conjunto de resultados. La suposición es que no hay vacíos en la identificación del registro, aunque dudo que afecte el resultado si los hubiera (aunque no lo he probado). También creé este procedimiento almacenado para ser más genérico; Pase el nombre de la tabla y el número de filas que se devolverán. Estoy ejecutando MySQL 5.5.38 en Windows 2008, 32 GB, E5450 dual de 3GHz, y en una tabla con 17.361.264 filas es bastante consistente en ~ .03 segundos / ~ 11 segundos para devolver 1,000,000 de filas. (los tiempos son de MySQL Workbench 6.1; también puede usar CEIL en lugar de FLOOR en la segunda instrucción de selección según su preferencia)

DELIMITER $$

USE [schema name] $$

DROP PROCEDURE IF EXISTS `random_rows` $$

CREATE PROCEDURE `random_rows`(IN tab_name VARCHAR(64), IN num_rows INT)
BEGIN

SET @t = CONCAT('SET @max=(SELECT MAX(id) FROM ',tab_name,')');
PREPARE stmt FROM @t;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

SET @t = CONCAT(
    'SELECT * FROM ',
    tab_name,
    ' WHERE id>FLOOR(RAND()*@max) LIMIT ',
    num_rows);

PREPARE stmt FROM @t;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END
$$

entonces

CALL [schema name].random_rows([table name], n);

3

Todas las mejores respuestas ya se han publicado (principalmente las que hacen referencia al enlace http://jan.kneschke.de/projects/mysql/order-by-rand/ ).

Quiero señalar otra posibilidad de aceleración: el almacenamiento en caché . Piense por qué necesita obtener filas aleatorias. Probablemente desee mostrar alguna publicación aleatoria o anuncio aleatorio en un sitio web. Si obtiene 100 req / s, ¿es realmente necesario que cada visitante obtenga filas aleatorias? Por lo general, está bien almacenar en caché estas X filas aleatorias durante 1 segundo (o incluso 10 segundos). No importa si 100 visitantes únicos en el mismo 1 segundo obtienen las mismas publicaciones aleatorias, porque el siguiente segundo, otros 100 visitantes obtendrán un conjunto diferente de publicaciones.

Al usar este almacenamiento en caché, puede usar también una de las soluciones más lentas para obtener datos aleatorios, ya que se obtendrán de MySQL solo una vez por segundo, independientemente de sus requisitos / s.


3

Mejoré la respuesta que tenía @Riedsio. Esta es la consulta más eficiente que puedo encontrar en una tabla grande, distribuida uniformemente con espacios (probado al obtener 1000 filas aleatorias de una tabla que tiene> 2.6B filas).

(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max := (SELECT MAX(id) FROM table)) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1)

Déjame desempacar lo que está pasando.

  1. @max := (SELECT MAX(id) FROM table)
    • Estoy calculando y guardando el máximo. Para tablas muy grandes, hay una ligera sobrecarga para calcular MAX(id)cada vez que necesita una fila
  2. SELECT FLOOR(rand() * @max) + 1 as rand)
    • Obtiene una identificación aleatoria
  3. SELECT id FROM table INNER JOIN (...) on id > rand LIMIT 1
    • Esto llena los vacíos. Básicamente, si selecciona aleatoriamente un número en los espacios, solo elegirá la siguiente identificación. Suponiendo que las brechas estén distribuidas uniformemente, esto no debería ser un problema.

Hacer la unión lo ayuda a encajar todo en 1 consulta para que pueda evitar hacer múltiples consultas. También le permite guardar los gastos generales de cálculo MAX(id). Dependiendo de su aplicación, esto puede importar mucho o muy poco.

Tenga en cuenta que esto solo obtiene los identificadores y los obtiene en orden aleatorio. Si desea hacer algo más avanzado, le recomiendo que haga esto:

SELECT t.id, t.name -- etc, etc
FROM table t
INNER JOIN (
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max := (SELECT MAX(id) FROM table)) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1)
) x ON x.id = t.id
ORDER BY t.id

Necesito 30 registros aleatorios, por lo que debería cambiar LIMIT 1a LIMIT 30todas partes en la consulta
Hassaan

@Hassaan no deberías, ese cambio LIMIT 1a LIMIT 30te daría 30 registros seguidos desde un punto aleatorio en la tabla. En su lugar, debe tener 30 copias de la (SELECT id FROM ....parte en el medio.
Hans Z

Lo he intentado pero no parece más eficiente que Riedsioresponder. He intentado con 500 visitas por segundo a la página usando PHP 7.0.22 y MariaDB en centos 7, con la Riedsiorespuesta obtuve más de 500 respuestas exitosas adicionales y luego tu respuesta.
Hassaan

1
La respuesta de @Hassaan riedsio da 1 fila, esta le da n filas, así como también reduce la sobrecarga de E / S para las consultas. Es posible que pueda obtener filas más rápido, pero con más carga en su sistema.
Hans Z

3

Utilicé este http://jan.kneschke.de/projects/mysql/order-by-rand/ publicado por Riedsio (utilicé el caso de un procedimiento almacenado que devuelve uno o más valores aleatorios):

   DROP TEMPORARY TABLE IF EXISTS rands;
   CREATE TEMPORARY TABLE rands ( rand_id INT );

    loop_me: LOOP
        IF cnt < 1 THEN
          LEAVE loop_me;
        END IF;

        INSERT INTO rands
           SELECT r1.id
             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;

        SET cnt = cnt - 1;
      END LOOP loop_me;

En el artículo, resuelve el problema de las brechas en los identificadores que causan resultados no tan aleatorios al mantener una tabla (usando desencadenantes, etc. ... vea el artículo); Estoy resolviendo el problema agregando otra columna a la tabla, poblada con números contiguos, comenzando desde 1 ( editar: esta columna se agrega a la tabla temporal creada por la subconsulta en tiempo de ejecución, no afecta su tabla permanente):

   DROP TEMPORARY TABLE IF EXISTS rands;
   CREATE TEMPORARY TABLE rands ( rand_id INT );

    loop_me: LOOP
        IF cnt < 1 THEN
          LEAVE loop_me;
        END IF;

        SET @no_gaps_id := 0;

        INSERT INTO rands
           SELECT r1.id
             FROM (SELECT id, @no_gaps_id := @no_gaps_id + 1 AS no_gaps_id FROM random) AS r1 JOIN
                  (SELECT (RAND() *
                                (SELECT COUNT(*)
                                   FROM random)) AS id)
                   AS r2
            WHERE r1.no_gaps_id >= r2.id
            ORDER BY r1.no_gaps_id ASC
            LIMIT 1;

        SET cnt = cnt - 1;
      END LOOP loop_me;

En el artículo, puedo ver que hizo todo lo posible para optimizar el código; No tengo idea de si / cuánto mis cambios afectan el rendimiento, pero funciona muy bien para mí.


"No tengo idea si / cuánto mis cambios afectan el rendimiento" - bastante. Para el @no_gaps_idíndice no se puede utilizar, por lo que si observa EXPLAINsu consulta, tiene Using filesorty Using where(sin índice) para las subconsultas, en contraste con la consulta original.
Fabian Schmengler

2

Aquí hay un cambio de juego que puede ser útil para muchos;

Tengo una tabla con 200k filas, con identificaciones secuenciales , necesitaba elegir N filas aleatorias, por lo que opto por generar valores aleatorios basados ​​en la ID más grande de la tabla, creé este script para descubrir cuál es la operación más rápida:

logTime();
query("SELECT COUNT(id) FROM tbl");
logTime();
query("SELECT MAX(id) FROM tbl");
logTime();
query("SELECT id FROM tbl ORDER BY id DESC LIMIT 1");
logTime();

Los resultados son:

  • Contar: 36.8418693542479 ms
  • Max: 0.241041183472 ms
  • Orden: 0.216960906982ms

En base a estos resultados, la descripción del pedido es la operación más rápida para obtener la identificación máxima.
Aquí está mi respuesta a la pregunta:

SELECT GROUP_CONCAT(n SEPARATOR ',') g FROM (
    SELECT FLOOR(RAND() * (
        SELECT id FROM tbl ORDER BY id DESC LIMIT 1
    )) n FROM tbl LIMIT 10) a

...
SELECT * FROM tbl WHERE id IN ($result);

FYI: para obtener 10 filas aleatorias de una tabla de 200k, me llevó 1,78 ms (incluidas todas las operaciones en el lado php)


3
Te sugiero que aumentes un LIMITpoco, puedes obtener duplicados.
Rick James

2

Esto es súper rápido y es 100% aleatorio incluso si tienes huecos.

  1. Cuenta el número x de filas que tiene disponiblesSELECT COUNT(*) as rows FROM TABLE
  2. Elige 10 números aleatorios distintos a_1,a_2,...,a_10 entre 0 yx
  3. Consulta tus filas de esta manera: SELECT * FROM TABLE LIMIT 1 offset a_ipara i = 1, ..., 10

Encontré este truco en el libro SQL Antipatterns de Bill Karwin .


Estaba pensando en la misma solución, por favor dígame, ¿es más rápido que el otro método?
G. Adnane

@ G.Adnane no es más rápido ni más lento que la respuesta aceptada, pero la respuesta aceptada supone una distribución equitativa de las identificaciones. No puedo imaginar ningún escenario en el que esto pueda garantizarse. Esta solución está en O (1) donde la solución SELECT column FROM table ORDER BY RAND() LIMIT 10está en O (nlog (n)). Entonces sí, esta es la solución en ayunas y funciona para cualquier distribución de identificadores.
Adam

no, porque en el enlace publicado para la solución aceptada, hay otros métodos, quiero saber si esta solución es más rápida que las otras, de otras maneras, podemos tratar de encontrar otra, es por eso que pregunto, de cualquier manera, +1 Para tu respuesta. Estaba usando lo mismo
G. Adnane

Hay un caso en el que desea obtener x número de filas, pero el desplazamiento va al final de la tabla que devolverá <x filas o solo 1 fila. no vi su respuesta antes de publicar la mía, pero lo
dejé

@ZOLDIK parece que elige las primeras 10 filas después del desplazamiento x. Yo diría que esta no es una generación aleatoria de 10 filas. En mi respuesta, debe ejecutar la consulta en el paso tres 10 veces, es decir, una solo obtiene una fila por ejecución y no tiene que preocuparse si el desplazamiento está al final de la tabla.
Adam

1

Si solo tiene una solicitud de lectura

Combina la respuesta de @redsio con una tabla temporal (600K no es tanto):

DROP TEMPORARY TABLE IF EXISTS tmp_randorder;
CREATE TABLE tmp_randorder (id int(11) not null auto_increment primary key, data_id int(11));
INSERT INTO tmp_randorder (data_id) select id from datatable;

Y luego tome una versión de @redsios Respuesta:

SELECT dt.*
FROM
       (SELECT (RAND() *
                     (SELECT MAX(id)
                        FROM tmp_randorder)) AS id)
        AS rnd
 INNER JOIN tmp_randorder rndo on rndo.id between rnd.id - 10 and rnd.id + 10
 INNER JOIN datatable AS dt on dt.id = rndo.data_id
 ORDER BY abs(rndo.id - rnd.id)
 LIMIT 1;

Si la mesa es grande, puede tamizar en la primera parte:

INSERT INTO tmp_randorder (data_id) select id from datatable where rand() < 0.01;

Si tiene muchas solicitudes de lectura

  1. Versión: puedes quedarte con la mesa tmp_randorder persistente, llámela datatable_idlist. Recree esa tabla en ciertos intervalos (día, hora), ya que también tendrá agujeros. Si su mesa se vuelve muy grande, también puede rellenar agujeros

    seleccione l.data_id como un todo de datatable_idlist l left join datatable dt on dt.id = l.data_id donde dt.id es nulo;

  2. Versión: asigne a su conjunto de datos una columna de orden aleatorio directamente en la tabla de datos o en una tabla adicional persistente datatable_sortorder. Indice esa columna. Genere un valor aleatorio en su aplicación (lo llamaré $rand).

    select l.*
    from datatable l 
    order by abs(random_sortorder - $rand) desc 
    limit 1;

Esta solución discrimina las 'filas de borde' con el orden aleatorio más alto y el más bajo, por lo que debe reorganizarlas en intervalos (una vez al día).


1

Otra solución simple sería clasificar las filas y buscar una de ellas al azar y con esta solución no necesitará tener ninguna columna basada en 'Id' en la tabla.

SELECT d.* FROM (
SELECT  t.*,  @rownum := @rownum + 1 AS rank
FROM mytable AS t,
    (SELECT @rownum := 0) AS r,
    (SELECT @cnt := (SELECT RAND() * (SELECT COUNT(*) FROM mytable))) AS n
) d WHERE rank >= @cnt LIMIT 10;

Puede cambiar el valor límite según su necesidad de acceder a tantas filas como desee, pero en su mayoría serían valores consecutivos.

Sin embargo, si no desea valores aleatorios consecutivos, puede obtener una muestra más grande y seleccionarla aleatoriamente. algo como ...

SELECT * FROM (
SELECT d.* FROM (
    SELECT  c.*,  @rownum := @rownum + 1 AS rank
    FROM buildbrain.`commits` AS c,
        (SELECT @rownum := 0) AS r,
        (SELECT @cnt := (SELECT RAND() * (SELECT COUNT(*) FROM buildbrain.`commits`))) AS rnd
) d 
WHERE rank >= @cnt LIMIT 10000 
) t ORDER BY RAND() LIMIT 10;

1

Una forma en que me parece bastante bueno si hay una identificación autogenerada es usar el operador de módulo '%'. Por ejemplo, si necesita 10,000 registros aleatorios de 70,000, podría simplificar esto diciendo que necesita 1 de cada 7 filas. Esto se puede simplificar en esta consulta:

SELECT * FROM 
    table 
WHERE 
    id % 
    FLOOR(
        (SELECT count(1) FROM table) 
        / 10000
    ) = 0;

Si el resultado de dividir las filas de destino por el total disponible no es un número entero, tendrá algunas filas adicionales de las que solicitó, por lo que debe agregar una cláusula LIMIT para ayudarlo a recortar el conjunto de resultados de esta manera:

SELECT * FROM 
    table 
WHERE 
    id % 
    FLOOR(
        (SELECT count(1) FROM table) 
        / 10000
    ) = 0
LIMIT 10000;

Esto requiere un escaneo completo, pero es más rápido que ORDER BY RAND, y en mi opinión es más fácil de entender que otras opciones mencionadas en este hilo. Además, si el sistema que escribe en la base de datos crea conjuntos de filas en lotes, es posible que no obtenga un resultado tan aleatorio como esperaba.


2
Ahora que lo creo, si necesita filas aleatorias cada vez que lo llame, esto es inútil. Solo estaba pensando en la necesidad de obtener filas aleatorias de un conjunto para investigar un poco. Todavía creo que el módulo es algo bueno para ayudar en el otro caso. Puede usar el módulo como filtro de primer paso para reducir el costo de una operación ORDER BY RAND.
Nicolas Cohen


1

He revisado todas las respuestas, y no creo que nadie mencione esta posibilidad en absoluto, y no estoy seguro de por qué.

Si desea la máxima simplicidad y velocidad, a un costo menor, entonces para mí parece tener sentido almacenar un número aleatorio en cada fila de la base de datos. Simplemente cree una columna adicional random_number, y configure su valor predeterminado en RAND(). Crea un índice en esta columna.

Luego, cuando desee recuperar una fila, genere un número aleatorio en su código (PHP, Perl, lo que sea) y compárelo con la columna.

SELECT FROM tbl WHERE random_number >= :random LIMIT 1

Supongo que aunque es muy ordenado para una sola fila, para diez filas como el OP le pidió que tuviera que llamarlo diez veces por separado (o presentar un ajuste inteligente que se me escape de inmediato)


Este es realmente un enfoque muy agradable y eficiente. El único inconveniente es el hecho de que intercambiaste espacio por velocidad, lo que parece un trato justo en mi opinión.
Tochukwu Nkemdilim

Gracias. Tuve un escenario en el que la tabla principal de la que quería una fila aleatoria tenía 5 millones de filas y muchas uniones, y después de intentar la mayoría de los enfoques en esta pregunta, este fue el error en el que me decidí. Una columna extra fue una compensación muy valiosa para mí.
Codemonkey

0

Lo siguiente debe ser rápido, imparcial e independiente de la columna de identificación. Sin embargo, no garantiza que el número de filas devueltas coincida con el número de filas solicitadas.

SELECT *
FROM t
WHERE RAND() < (SELECT 10 / COUNT(*) FROM t)

Explicación: suponiendo que desea 10 filas de 100, cada fila tiene 1/10 de probabilidad de ser SELECCIONADO, lo que podría lograrse mediante WHERE RAND() < 0.1. Este enfoque no garantiza 10 filas; pero si la consulta se ejecuta suficientes veces, el número promedio de filas por ejecución será de alrededor de 10 y cada fila de la tabla se seleccionará de manera uniforme.


0

Puede usar fácilmente un desplazamiento aleatorio con un límite

PREPARE stm from 'select * from table limit 10 offset ?';
SET @total = (select count(*) from table);
SET @_offset = FLOOR(RAND() * @total);
EXECUTE stm using @_offset;

También puede aplicar una cláusula where así

PREPARE stm from 'select * from table where available=true limit 10 offset ?';
SET @total = (select count(*) from table where available=true);
SET @_offset = FLOOR(RAND() * @total);
EXECUTE stm using @_offset;

Probado en 600,000 filas (700MB), la ejecución de la consulta de la tabla tomó ~ 0.016sec HDD drive

--EDIT--
   El desplazamiento puede tomar un valor cercano al final de la tabla, lo que dará como resultado que la instrucción select devuelva menos filas (o tal vez solo 1 fila), para evitar esto, podemos verificar offsetnuevamente después de declararlo, así

SET @rows_count = 10;
PREPARE stm from "select * from table where available=true limit ? offset ?";
SET @total = (select count(*) from table where available=true);
SET @_offset = FLOOR(RAND() * @total);
SET @_offset = (SELECT IF(@total-@_offset<@rows_count,@_offset-@rows_count,@_offset));
SET @_offset = (SELECT IF(@_offset<0,0,@_offset));
EXECUTE stm using @rows_count,@_offset;

-1

Yo uso esta consulta:

select floor(RAND() * (SELECT MAX(key) FROM table)) from table limit 10

tiempo de consulta: 0.016s


Tener PKs como 1,2,9,15. por la consulta anterior obtendrá filas como 4, 7, 14, 11 que son insuficientes!
Junaid Atari

-2

Así es como lo hago:

select * 
from table_with_600k_rows
where rand() < 10/600000
limit 10

Me gusta porque no requiere otras tablas, es simple de escribir y es muy rápido de ejecutar.


55
Es un escaneo completo de la tabla y no utiliza ningún índice. Para mesas grandes y ambiente ocupado, eso es grande no no.
mate

-2

Use la consulta simple a continuación para obtener datos aleatorios de una tabla.

SELECT user_firstname ,
COUNT(DISTINCT usr_fk_id) cnt
FROM userdetails 
GROUP BY usr_fk_id 
ORDER BY cnt ASC  
LIMIT 10

Si desea usar cualquier declaración de unión y dónde puede usar el filtro.
MANOJ

3
¿De qué parte de la consulta obtienes el azar?
Marki555

-4

Supongo que esta es la mejor manera posible ...

SELECT id, id * RAND( ) AS random_no, first_name, last_name
FROM user
ORDER BY random_no

8
Demonios, no, esa es una de las peores formas de obtener filas aleatorias de la tabla. Eso es exploración de tabla completa + clasificación de archivos + tabla tmp = mal rendimiento.
mate

1
Además del rendimiento, también está lejos de ser perfectamente aleatorio; está ordenando por el producto de la identificación y un número aleatorio, en lugar de simplemente ordenar por un número aleatorio, lo que significa que las filas con identificadores más bajos estarán sesgadas para aparecer antes en su conjunto de resultados.
Mark Amery el
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.