MySQL seleziona velocemente 10 righe casuali da 600K righe


463

Come posso scrivere al meglio una query che seleziona casualmente 10 righe da un totale di 600k?


15
Ecco 8 tecniche ; forse uno funzionerà bene nel tuo caso.
Rick James,

Risposte:


386

Un ottimo post che gestisce diversi casi, da semplici, a spazi vuoti, a non uniformi con spazi vuoti.

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

Per il caso più generale, ecco come lo fai:

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

Ciò suppone che la distribuzione degli ID sia uguale e che possano esserci lacune nell'elenco degli ID. Vedi l'articolo per esempi più avanzati


52
Sì, se hai potenzialmente grandi lacune negli ID, allora la possibilità che il tuo ID più basso venga scelto casualmente è molto più bassa dei tuoi ID alti. In effetti, la possibilità che il primo ID dopo il gap più ampio venga effettivamente raccolto è in realtà il più alto. Pertanto, questo non è casuale per definizione.
lukeocodes,

6
Come si ottengono 10 diverse righe casuali? Devi impostare il limite su 10 e quindi iterare 10 volte con mysqli_fetch_assoc($result)? O quei 10 risultati non sono necessariamente distinguibili?
Adam,

12
Casuale richiede una pari possibilità per qualsiasi risultato, nella mia mente. ;)
lukeocodes

4
L'articolo completo affronta questioni come distribuzioni disuguali e risultati ripetuti.
Bradd Szonye,

1
in particolare, se hai un gap all'inizio dei tuoi ID, il primo verrà selezionato (min / max-min) del tempo. In tal caso, una semplice modifica è MAX () - MIN () * RAND + MIN (), che non è troppo lento.
Abominatore del codice

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

Non è la soluzione efficiente ma funziona


139
ORDER BY RAND()è relativamente lento
Mateusz Charytoniuk il

7
Mateusz - prova pls, SELECT words, transcription, translation, sound FROM vocabulary WHERE menu_id=$menuId ORDER BY RAND() LIMIT 10prende 0,0010, senza LIMIT 10 ci sono voluti 0,0012 (in quella tabella 3500 parole).
Arthur Kushman

26
@zeusakm 3500 parole non è poi così tanto; il problema è che esplode oltre un certo punto perché MySQL deve effettivamente ordinare TUTTI i record dopo averne letto ciascuno; una volta che l'operazione colpisce il disco rigido, puoi sentire la differenza.
Ja͢ck

16
Non voglio ripetermi, ma di nuovo, questa è la scansione della tabella completa. Sui tavoli di grandi dimensioni è molto dispendioso in termini di tempo e memoria e potrebbe causare la creazione e il funzionamento di tabelle temporanee su disco che sono molto lente.
matt

10
Quando stavo intervistando Facebook nel 2010, mi hanno chiesto come selezionare un disco casuale da un enorme file di dimensioni sconosciute, in una lettura. Una volta che hai avuto un'idea, è facile generalizzarla per selezionare più record. Quindi sì, l'ordinamento dell'intero file è ridicolo. Allo stesso tempo, è molto utile. Ho appena usato questo approccio per selezionare 10 righe casuali da una tabella con oltre 1.000.000 di righe. Certo, ho dovuto aspettare un po '; ma volevo solo avere un'idea di come fossero le righe tipiche in questa tabella ...
osa,

27

Query semplice che ha prestazioni eccellenti e funziona con lacune :

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

Questa query su una tabella 200K richiede 0,08 secondi e la versione normale (SELEZIONA * DA tbl ORDINA PER RAND () LIMIT 10) richiede 0,35 secondi sulla mia macchina.

Questo è veloce perché la fase di ordinamento utilizza solo la colonna ID indicizzata. Puoi vedere questo comportamento nella spiegazione:

SELEZIONA * DA tbl ORDINA PER RAND () LIMIT 10: Spiegazione semplice

SELEZIONA * DA tbl COME t1 UNISCITI (SELEZIONA ID DA tbl ORDINA PER RAND () LIMIT 10) come t2 ON t1.id = t2.id inserisci qui la descrizione dell'immagine

Versione ponderata : https://stackoverflow.com/a/41577458/893432


1
Scusa, ho provato! prestazioni lente su dischi da 600k.
Dylan B,

@DylanB Ho aggiornato la risposta con un test.
Ali,

17

Ricevo query veloci (circa 0,5 secondi) con una CPU lenta , selezionando 10 righe casuali in un registro di MySQL da 400 GB di dimensioni non memorizzate nella cache di 2 GB. Vedi qui il mio codice: selezione rapida di righe casuali in 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
Dato il mio tavolo di oltre 14 milioni di dischi, questo è lento quantoORDER BY RAND()
Fabrizio,

5
@snippetsofcode Nel tuo caso - 400k di righe puoi usare semplicemente "ORDER BY rand ()". Il tuo trucco con 3 domande è inutile. Puoi riscriverlo come "SELEZIONA ID, url DA pagine DOVE ID IN (SELEZIONA ID DA pagine ORDINA PER rand () LIMIT 10)"
Roman Podlinov

4
La tua tecnica esegue ancora una scansione della tabella. Usalo FLUSH STATUS; SELECT ...; SHOW SESSION STATUS LIKE 'Handler%';per vederlo.
Rick James,

4
Prova anche a eseguire quella query nella pagina web di 200 req / s. La concorrenza ti ucciderà.
Marki555,

Il vantaggio di @RomanPodlinov ORDER BY RAND()è che ordina solo gli ID (non le righe intere), quindi la tabella temporanea è più piccola, ma deve comunque ordinarle tutte.
Marki555,

16

È una query molto semplice e a riga singola.

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

21
Cordiali saluti, order by rand()è molto lento se il tavolo è grande
evilReiko

6
A volte lo SLOW è accettato se voglio mantenerlo SEMPLICE

L'indicizzazione dovrebbe essere applicata sul tavolo se è grande.
Muhammad Azeem,

1
L'indicizzazione non aiuta qui. Gli indici sono utili per cose molto specifiche e questa query non è una di queste.
Andrew,

13

Dal libro:

Scegli una riga casuale usando un offset

Ancora un'altra tecnica che evita i problemi riscontrati nelle alternative precedenti è quella di contare le righe nel set di dati e restituire un numero casuale compreso tra 0 e il conteggio. Quindi utilizzare questo numero come offset quando si esegue una query sul set di dati

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

Utilizza questa soluzione quando non puoi assumere valori chiave contigui e devi assicurarti che ogni riga abbia una probabilità pari di essere selezionata.


1
per tavoli molto grandi, SELECT count(*)diventa lento.
Hans Z,

7

Come selezionare righe casuali da una tabella:

Da qui: selezionare le righe casuali in MySQL

Un rapido miglioramento rispetto alla "scansione delle tabelle" consiste nell'utilizzare l'indice per raccogliere ID casuali.

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

1
Questo aiuta alcuni per MyISAM, ma non per InnoDB (supponendo che id sia il cluster PRIMARY KEY).
Rick James,

7

Bene, se non hai spazi vuoti nelle tue chiavi e sono tutti numerici, puoi calcolare numeri casuali e selezionare quelle righe. ma questo probabilmente non sarà il caso.

Quindi una soluzione sarebbe la seguente:

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

che sostanzialmente assicurerà di ottenere un numero casuale nell'intervallo dei tasti e quindi selezionare il migliore successivo che è maggiore. devi farlo 10 volte.

tuttavia questo NON è davvero casuale perché molto probabilmente le tue chiavi non saranno distribuite uniformemente.

È davvero un grosso problema e non è facile da risolvere che soddisfa tutti i requisiti, il rand () di MySQL è il migliore che puoi ottenere se vuoi davvero 10 righe casuali.

C'è comunque un'altra soluzione che è veloce ma ha anche un compromesso quando si tratta di casualità, ma potrebbe adattarsi meglio a te. Leggi qui: Come posso ottimizzare la funzione ORDER BY RAND () di MySQL?

La domanda è quanto ti serve che sia casuale.

Puoi spiegarci un po 'di più in modo che io possa darti una buona soluzione.

Ad esempio, un'azienda con cui ho lavorato aveva una soluzione in cui avevano bisogno di casualità assoluta estremamente veloce. Alla fine hanno pre-popolato il database con valori casuali che sono stati selezionati in ordine decrescente e successivamente impostati su valori casuali diversi.

Se non aggiorni quasi mai, potresti anche riempire un ID incrementale in modo da non avere spazi vuoti e puoi semplicemente calcolare i tasti casuali prima di selezionare ... Dipende dal caso d'uso!


Ciao Joe In questo caso particolare le chiavi non devono mancare di spazio, ma nel tempo ciò può cambiare. E mentre la tua risposta funziona, genererà le 10 righe casuali (a condizione che scriva il limite 10) che sono consecutive e volevo più casualità per così dire. :) Grazie.
Francisc,

Se hai bisogno di 10 usa una sorta di unione per generare 10 righe univoche.
johno,

questo è quello che ho detto. devi eseguirlo 10 volte. combinare l'unione wition è un modo per inserirlo in una query. vedi il mio addendum 2 minuti fa.
The Surrican,

1
@TheSurrican, Questa soluzione sembra interessante ma è altamente imperfetta . Prova a inserire solo uno molto grande Ide tutte le tue domande casuali ti restituiranno quello Id.
Pacerier

1
FLOOR(RAND()*MAX(id))è distorto verso la restituzione di ID più grandi.
Rick James,

3

Avevo bisogno di una query per restituire un gran numero di righe casuali da una tabella piuttosto grande. Questo è quello che mi è venuto in mente. Per prima cosa ottieni l'ID record massimo:

SELECT MAX(id) FROM table_name;

Quindi sostituire quel valore in:

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

Dove max è l'ID record massimo nella tabella e n è il numero di righe desiderate nel set di risultati. L'ipotesi è che non ci siano lacune nell'ID record, anche se dubito che ciò influirebbe sul risultato se non ci fosse (non l'ho provato però). Ho anche creato questa procedura memorizzata per essere più generica; passare il nome della tabella e il numero di righe da restituire. Sto eseguendo MySQL 5.5.38 su Windows 2008, 32 GB, doppio 3GHz E5450 e su una tabella con 17.361.264 righe è abbastanza coerente a ~ .03 sec / ~ 11 sec per restituire 1.000.000 di righe. (i tempi sono di MySQL Workbench 6.1; puoi anche usare CEIL invece di FLOOR nella seconda istruzione select a seconda delle tue preferenze)

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
$$

poi

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

3

Tutte le risposte migliori sono già state pubblicate (principalmente quelle che fanno riferimento al link http://jan.kneschke.de/projects/mysql/order-by-rand/ ).

Voglio individuare un'altra possibilità di accelerazione: la memorizzazione nella cache . Pensa al motivo per cui devi ottenere righe casuali. Probabilmente vuoi visualizzare alcuni post casuali o annunci casuali su un sito web. Se ricevi 100 req / s, è davvero necessario che ogni visitatore ottenga righe casuali? Di solito è del tutto corretto memorizzare nella cache queste X righe casuali per 1 secondo (o anche 10 secondi). Non importa se 100 visitatori unici nello stesso 1 secondo ottengono gli stessi post casuali, perché il secondo successivo altri 100 visitatori riceveranno un set di post diverso.

Quando si utilizza questa memorizzazione nella cache, è possibile utilizzare anche alcune delle soluzioni più lente per ottenere i dati casuali in quanto verranno recuperati da MySQL solo una volta al secondo, indipendentemente dalle richieste.


3

Ho migliorato la risposta di @Riedsio. Questa è la query più efficiente che riesco a trovare su una tabella di grandi dimensioni, distribuita in modo uniforme con lacune (testata per ottenere 1000 righe casuali da una tabella con> 2.6B righe).

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

Lasciami spacchettare cosa sta succedendo.

  1. @max := (SELECT MAX(id) FROM table)
    • Sto calcolando e salvando il massimo. Per tabelle molto grandi, c'è un leggero sovraccarico per il calcolo MAX(id)ogni volta che è necessaria una riga
  2. SELECT FLOOR(rand() * @max) + 1 as rand)
    • Ottiene un ID casuale
  3. SELECT id FROM table INNER JOIN (...) on id > rand LIMIT 1
    • Questo colma le lacune. Fondamentalmente se selezioni casualmente un numero negli spazi vuoti, sceglierà semplicemente l'id successivo. Supponendo che gli spazi siano distribuiti uniformemente, questo non dovrebbe essere un problema.

Fare l'unione ti aiuta ad adattare tutto in 1 query in modo da evitare di fare più query. Inoltre, consente di risparmiare il sovraccarico del calcolo MAX(id). A seconda dell'applicazione, questo potrebbe importare molto o molto poco.

Si noti che questo ottiene solo gli ID e li ottiene in ordine casuale. Se vuoi fare qualcosa di più avanzato ti consiglio di farlo:

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

Ho bisogno di 30 record casuale, quindi dovrei cambiare LIMIT 1per LIMIT 30tutto il mondo nella query
Hassaan

@Hassaan non dovresti, che cambiando LIMIT 1a LIMIT 30otterrai 30 record di fila da un punto casuale nella tabella. Dovresti invece avere 30 copie della (SELECT id FROM ....parte nel mezzo.
Hans Z,

Ho provato ma non sembra più efficiente quindi Riedsiorispondi. Ho provato con 500 hit al secondo sulla pagina usando PHP 7.0.22 e MariaDB su centos 7, con Riedsiorisposta ho ottenuto oltre 500 risposte extra di successo quindi la tua risposta.
Hassaan,

1
La risposta di @Hassaan riedsio dà 1 riga, questa ti dà n righe, oltre a ridurre le spese generali di I / O per l'interrogazione. Potresti essere in grado di ottenere file più velocemente, ma con un carico maggiore sul tuo sistema.
Hans Z,

3

Ho usato questo http://jan.kneschke.de/projects/mysql/order-by-rand/ pubblicato da Riedsio (ho usato il caso di una procedura memorizzata che restituisce uno o più valori casuali):

   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;

Nell'articolo risolve il problema delle lacune negli ID che causano risultati non casuali mantenendo una tabella (usando i trigger, ecc ... vedi l'articolo); Sto risolvendo il problema aggiungendo un'altra colonna alla tabella, popolata con numeri contigui, a partire da 1 ( modifica: questa colonna viene aggiunta alla tabella temporanea creata dalla subquery in fase di runtime, non influisce sulla tabella 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;

Nell'articolo vedo che ha fatto di tutto per ottimizzare il codice; non ho idea se / quanto i miei cambiamenti abbiano un impatto sulle prestazioni ma per me funziona molto bene.


"Non ho idea se / quanto i miei cambiamenti influenzino le prestazioni" - abbastanza. Per @no_gaps_idnessun indice può essere usato, quindi se cerchi la EXPLAINtua query, hai Using filesorte Using where(senza indice) per le subquery, in contrasto con la query originale.
Fabian Schmengler,

2

Ecco un punto di svolta che può essere utile per molti;

Ho una tabella con 200k righe, con ID sequenziali , dovevo scegliere N righe casuali, quindi ho scelto di generare valori casuali basati sull'ID più grande nella tabella, ho creato questo script per scoprire qual è l'operazione più veloce:

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

I risultati sono:

  • Contare: 36.8418693542479 ms
  • Max: 0.241041183472 ms
  • Ordine: 0.216960906982ms

Sulla base di questi risultati, la descrizione dell'ordine è l'operazione più veloce per ottenere l'id massimo,
ecco la mia risposta alla domanda:

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

Cordiali saluti: Per ottenere 10 righe casuali da una tabella 200k, mi ci sono voluti 1,78 ms (comprese tutte le operazioni sul lato php)


3
Suggerisci di aumentare LIMITleggermente: puoi ottenere duplicati.
Rick James,

2

Questo è super veloce ed è casuale al 100% anche se hai delle lacune.

  1. Contare il numero xdi righe disponibiliSELECT COUNT(*) as rows FROM TABLE
  2. Scegli 10 numeri casuali distinti a_1,a_2,...,a_10tra 0 ex
  3. Interroga le tue righe in questo modo: SELECT * FROM TABLE LIMIT 1 offset a_iper i = 1, ..., 10

Ho trovato questo trucco nel libro SQL Antipatterns di Bill Karwin .


Stavo pensando alla stessa soluzione, per favore dimmi, è più veloce del metodo degli altri?
G. Adnane,

@ G.Adnane non è più veloce o più lento della risposta accettata, ma la risposta accettata presuppone una distribuzione equa degli ID. Non riesco a immaginare uno scenario in cui questo può essere garantito. Questa soluzione è in O (1) dove la soluzione SELECT column FROM table ORDER BY RAND() LIMIT 10è in O (nlog (n)). Quindi sì, questa è la soluzione veloce e funziona per qualsiasi distribuzione di ID.
Adam

no, perché nel link pubblicato per la soluzione accettata, ci sono altri metodi, voglio sapere se questa soluzione è più veloce delle altre, in altri modi, possiamo provare a trovarne un'altra, ecco perché Iam chiede, in ogni caso, +1 per la tua risposta. Stavo usando il samething
G. Adnane,

c'è un caso in cui si desidera ottenere il numero x di righe ma l'offset va alla fine della tabella che restituirà <x righe o solo 1 riga. non ho visto la tua risposta prima ho postato la mia, ma ho fatto più chiaro qui stackoverflow.com/a/59981772/10387008
ZOLDIK

@ZOLDIK sembra che tu scelga le prime 10 righe dopo l'offset x. Direi che questa non è una generazione casuale di 10 righe. Nella mia risposta, è necessario eseguire la query nel passaggio tre 10 volte, ovvero si ottiene solo una riga per esecuzione e non preoccuparsi se l'offset si trova alla fine della tabella.
Adam,

1

Se hai solo una richiesta di lettura

Combina la risposta di @redsio con una tabella temporanea (600K non è poi così 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;

E poi prendi una versione di @redsios Risposta:

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;

Se il tavolo è grande, puoi setacciare nella prima parte:

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

Se hai molte richieste di lettura

  1. Versione: è possibile mantenere il tavolo tmp_randorder persistente , chiamarla datatable_idlist. Ricrea la tabella in determinati intervalli (giorno, ora), poiché anche avrà buchi. Se il tuo tavolo diventa molto grande, puoi anche riempire i buchi

    selezionare l.data_id come intero da datatable_idlist l sinistra unire datatable dt su dt.id = l.data_id dove dt.id è nullo;

  2. Versione: assegna al tuo set di dati una colonna random_sortorder direttamente nella tabella dei dati o in una tabella aggiuntiva persistente datatable_sortorder. Indicizza quella colonna. Genera un valore casuale nella tua applicazione (lo chiamerò $rand).

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

Questa soluzione discrimina le 'righe di bordo' con il più alto e il più basso random_sortorder, quindi riorganizzali a intervalli (una volta al giorno).


1

Un'altra soluzione semplice sarebbe classificare le righe e recuperarne una in modo casuale e con questa soluzione non sarà necessario avere alcuna colonna basata su "ID" nella tabella.

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;

È possibile modificare il valore limite in base alla necessità di accedere a tutte le righe che si desidera, ma si tratterebbe principalmente di valori consecutivi.

Tuttavia, se non desideri valori casuali consecutivi, puoi prelevare un campione più grande e selezionarlo a caso. qualcosa di simile a ...

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

Un modo che trovo abbastanza buono se esiste un ID generato automaticamente è usare l'operatore modulo '%'. Ad esempio, se sono necessari 10.000 record casuali su 70.000, è possibile semplificare dicendo che è necessario 1 su ogni 7 righe. Questo può essere semplificato in questa query:

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

Se il risultato della divisione delle righe di destinazione per il totale disponibile non è un numero intero, avrai alcune righe extra rispetto a quanto richiesto, quindi dovresti aggiungere una clausola LIMIT per aiutarti a tagliare il set di risultati in questo modo:

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

Ciò richiede una scansione completa, ma è più veloce di ORDER BY RAND e, a mio avviso, più semplice da comprendere rispetto ad altre opzioni menzionate in questo thread. Inoltre, se il sistema che scrive nel DB crea gruppi di righe in batch, è possibile che non si ottenga un risultato così casuale come previsto.


2
Ora che penso di sì, se hai bisogno di righe casuali ogni volta che lo chiami, questo è inutile. Stavo solo pensando alla necessità di ottenere file casuali da un set per fare qualche ricerca. Penso ancora che il modulo sia una buona cosa per aiutare nell'altro caso. È possibile utilizzare modulo come filtro di primo passaggio per ridurre il costo di un'operazione ORDER BY RAND.
Nicolas Cohen,


1

Ho esaminato tutte le risposte e non credo che nessuno menzioni affatto questa possibilità e non sono sicuro del perché.

Se vuoi la massima semplicità e velocità, a un costo minore, allora per me sembra sensato memorizzare un numero casuale su ogni riga nel DB. Basta creare una colonna aggiuntiva random_numbere impostarne il valore predefinito su RAND(). Crea un indice su questa colonna.

Quindi, quando vuoi recuperare una riga, genera un numero casuale nel tuo codice (PHP, Perl, qualunque cosa) e confrontalo con la colonna.

SELECT FROM tbl WHERE random_number >= :random LIMIT 1

Immagino che, sebbene sia molto pulito per una singola riga, per dieci righe come l'OP ha chiesto che avresti dovuto chiamarlo dieci volte separate (o inventare una modifica intelligente che mi sfugge immediatamente)


Questo è in realtà un approccio molto carino ed efficiente. L'unico inconveniente è il fatto che hai scambiato spazio per la velocità, che secondo me sembra un buon affare.
Tochukwu Nkemdilim

Grazie. Ho avuto uno scenario in cui la tabella principale da cui volevo una riga casuale aveva 5 milioni di righe e un sacco di join, e dopo aver provato la maggior parte degli approcci in questa domanda questo era il kludge su cui mi sono deciso. Una colonna in più è stata un compromesso molto utile, per me.
Codemonkey

0

Quanto segue dovrebbe essere veloce, imparziale e indipendente dalla colonna ID. Tuttavia, non garantisce che il numero di righe restituite corrisponderà al numero di righe richieste.

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

Spiegazione: supponendo che si desideri 10 righe su 100, ogni riga ha 1/10 di probabilità di essere SELEZIONATA che potrebbe essere raggiunta WHERE RAND() < 0.1. Questo approccio non garantisce 10 righe; ma se la query viene eseguita abbastanza volte il numero medio di righe per esecuzione sarà circa 10 e ogni riga nella tabella verrà selezionata in modo uniforme.


0

È possibile utilizzare facilmente un offset casuale con un limite

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

Puoi anche applicare una clausola where in questo modo

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;

Testato su 600.000 righe (700 MB) l'esecuzione della query della tabella ha richiesto ~ 0,016

sec unità disco fisso - EDIT -
   L'offset potrebbe assumere un valore vicino alla fine della tabella, il che comporterà che l'istruzione select restituisce meno righe (o forse solo 1 riga), per evitare ciò possiamo ricontrollare offsetdopo averlo dichiarato, in questo modo

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

Uso questa query:

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

tempo di query: 0,016 s


Avere PK come 1,2,9,15. con la query sopra otterrai righe come 4, 7, 14, 11 che sono insufficienti!
Junaid Atari,

-2

Ecco come lo faccio:

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

Mi piace perché non richiede altre tabelle, è semplice da scrivere ed è molto veloce da eseguire.


5
Questa è la scansione completa della tabella e non utilizza alcun indice. Per tavoli di grandi dimensioni e ambienti affollati è grande no no.
matt

-2

Utilizzare la query semplice di seguito per ottenere dati casuali da una tabella.

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

Se si desidera utilizzare un'istruzione join e in quale filtro è possibile utilizzare.
MANOJ,

3
Da quale parte della query ottieni la casualità?
Marki555,

-4

Immagino che questo sia il miglior modo possibile ..

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

8
Inferno no, questo è uno dei modi peggiori per ottenere righe casuali dalla tabella. Questa è la scansione completa della tabella + filesort + tabella tmp = cattive prestazioni.
matt

1
Oltre alle prestazioni, è anche tutt'altro che perfettamente casuale; stai ordinando in base al prodotto dell'id e un numero casuale, piuttosto che semplicemente ordinando per un numero casuale, il che significa che le righe con ID inferiori saranno distorte per apparire prima nel tuo set di risultati.
Mark Amery,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.