web-dev-qa-db-fra.com

MySQL: Alternatives à ORDER BY Rand ()

J'ai lu quelques informations sur quelques alternatives à la fonction ORDER BY Rand() de MySQL, mais la plupart des alternatives ne s'appliquent qu'à l'endroit où un seul résultat aléatoire est nécessaire. 

Quelqu'un a-t-il une idée de la façon d'optimiser une requête qui renvoie plusieurs résultats aléatoires, tels que: 

   SELECT u.id, 
          p.photo 
     FROM users u, profiles p 
    WHERE p.memberid = u.id 
      AND p.photo != '' 
      AND (u.ownership=1 OR u.stamp=1) 
 ORDER BY Rand() 
    LIMIT 18 
54
Tony

MISE À JOUR 2016

Cette solution fonctionne mieux avec une colonne indexée .

Voici un exemple simple de banc de requête optimisé portant 100 000 lignes. 

OPTIMISE: 300ms

SELECT 
    g.*
FROM
    table g
        JOIN
    (SELECT 
        id
    FROM
        table
    WHERE
        Rand() < (SELECT 
                ((4 / COUNT(*)) * 10)
            FROM
                table)
    ORDER BY Rand()
    LIMIT 4) AS z ON z.id= g.id

remarque sur la quantité maximale : limite 4 et 4/compte (*). Les 4 doivent être le même nombre. Changer le nombre que vous retournez n'influe pas beaucoup sur la vitesse. L'indice de référence à la limite 4 et à la limite 1000 est identique. Limite de 10 000 prises jusqu'à 600ms

note sur la jointure : La randomisation de l'id est plus rapide que la randomisation d'une ligne entière. Puisqu'il doit copier toute la ligne dans la mémoire, randomisez-le. La jointure peut être n'importe quelle table liée à la sous-requête Its pour empêcher les analyses de table.

note where clause : Le nombre where limite le nombre de résultats randomisés. Il faut un pourcentage des résultats et les trie plutôt que la table entière. 

note sous-requête : Les conditions si vous faites des jointures et des clauses extra où vous devez les placer à la fois dans la sous-requête et dans la sous-requête. Pour avoir un compte précis et récupérer des données correctes.

NON UTILISÉ: 1200ms

SELECT 
    g.*
FROM
    table g
ORDER BY Rand()
LIMIT 4

AVANTAGES 

4x plus rapide que order by Rand(). Cette solution peut fonctionner avec n'importe quelle table avec une colonne indexée. 

LES INCONV&EACUTE;NIENTS

C'est un peu complexe avec des requêtes complexes. Besoin de maintenir 2 bases de code dans les sous-requêtes

27
Neoaptt

Voici une alternative, mais elle repose toujours sur l'utilisation de Rand ():

  SELECT u.id, 
         p.photo,
         ROUND(Rand() * x.m_id) 'Rand_ind'
    FROM users u, 
         profiles p,
         (SELECT MAX(t.id) 'm_id'
            FROM USERS t) x
   WHERE p.memberid = u.id 
     AND p.photo != '' 
     AND (u.ownership=1 OR u.stamp=1) 
ORDER BY Rand_ind
   LIMIT 18

Ceci est légèrement plus complexe, mais donne une meilleure distribution des valeurs random_ind:

  SELECT u.id, 
         p.photo,
         FLOOR(1 + Rand() * x.m_id) 'Rand_ind'
    FROM users u, 
         profiles p,
         (SELECT MAX(t.id) - 1 'm_id'
            FROM USERS t) x
   WHERE p.memberid = u.id 
     AND p.photo != '' 
     AND (u.ownership=1 OR u.stamp=1) 
ORDER BY Rand_ind
   LIMIT 18
20
OMG Ponies

Ce n’est pas le chemin le plus rapide, mais le plus rapide puis le plus commun ORDER BY Rand():

ORDER BY Rand() n'est pas si lent lorsque vous l'utilisez pour rechercher uniquement la colonne indexée. Vous pouvez prendre tous vos identifiants dans une requête comme ceci:

SELECT id
FROM testTable
ORDER BY Rand();

pour obtenir une séquence d'identifiants aléatoires et JOIN le résultat d'une autre requête avec d'autres paramètres SELECT ou WHERE:

SELECT t.*
FROM testTable t
JOIN
    (SELECT id
    FROM `testTable`
    ORDER BY Rand()) AS z ON z.id= t.id   
WHERE t.isVisible = 1
LIMIT 100; 

dans votre cas ce serait:

SELECT u.id, p.photo 
FROM users u, profiles p 
JOIN
    (SELECT id
    FROM users
    ORDER BY Rand()) AS z ON z.id = u.id   
WHERE p.memberid = u.id 
  AND p.photo != '' 
  AND (u.ownership=1 OR u.stamp=1) 
LIMIT 18 

C'est une méthode très directe et cela peut ne pas convenir avec de très grandes tables, mais c'est quand même plus rapide que Rand(). J'ai obtenu un temps d'exécution 20 fois plus rapide en recherchant 3 000 lignes aléatoires sur près de 400 000.

6
Adlaran

Créez une colonne ou joignez-vous à une sélection avec des nombres aléatoires (générés par exemple en php) et triez-la à l'aide de cette colonne.

1
Pethő Jonatán

Je me suis heurté à cela aujourd'hui et j'essayais d'utiliser «DISTINCT» avec JOIN, mais je supposais qu'il y avait des doublons, car Rand faisait en sorte que chaque ligne JOIN soit distincte. Je me suis un peu embrouillé et j'ai trouvé une solution qui fonctionne, comme ceci:

SELECT DISTINCT t.id, 
                t.photo 
       FROM (SELECT  u.id, 
                     p.photo,
                     Rand() as Rand
                FROM users u, profiles p 
                 WHERE p.memberid = u.id 
                  AND p.photo != '' 
                  AND (u.ownership=1 OR u.stamp=1)
                ORDER BY Rand) t
       LIMIT 18
1
Joe T

Order by Rand() est très lent sur les grandes tables, 

J'ai trouvé la solution suivante dans un script php:

Select min(id) as min, max(id) as max from table;

Puis faites au hasard en php

$Rand = Rand($min, $max);

Ensuite

'Select * from table where id>'.$Rand.' limit 1';

Semble être assez rapide ....

1
tonio

La solution que j'utilise est également affichée dans le lien ci-dessous: Comment puis-je optimiser la fonction ORDER BY Rand () de MySQL?

Je suppose que votre table d'utilisateurs sera plus grande que votre table de profils, sinon c'est une cardinalité de 1 à 1.

Si c'est le cas, je commencerais par faire une sélection aléatoire sur la table utilisateur avant de rejoindre la table de profil.

D'abord faire la sélection:

SELECT *
FROM users
WHERE users.ownership = 1 OR users.stamp = 1

Ensuite, à partir de ce pool, sélectionnez des lignes aléatoires en fonction de la probabilité calculée. Si votre table comporte M lignes et que vous souhaitez sélectionner N lignes aléatoires, la probabilité de sélection aléatoire doit être N/M. Par conséquent:

SELECT *
FROM
(
    SELECT *
    FROM users
    WHERE users.ownership = 1 OR users.stamp = 1
) as U
WHERE 
    Rand() <= $limitCount / (SELECT count(*) FROM users WHERE users.ownership = 1 OR users.stamp = 1)

Où N est $ limitCount et M est la sous-requête qui calcule le nombre de lignes de la table. Cependant, comme nous travaillons sur la probabilité, il est possible que LESS soit inférieur à $ limitCount. Par conséquent, nous devrions multiplier N par un facteur pour augmenter la taille du pool aléatoire.

c'est à dire:

SELECT*
FROM
(
    SELECT *
    FROM users
    WHERE users.ownership = 1 OR users.stamp = 1
) as U
WHERE 
    Rand() <= $limitCount * $factor / (SELECT count(*) FROM users WHERE users.ownership = 1 OR users.stamp = 1)

Je règle généralement $ factor = 2. Vous pouvez définir le facteur sur une valeur inférieure pour réduire davantage la taille du pool aléatoire (par exemple 1,5). 

À ce stade, nous aurions déjà limité une table de taille M à une taille d'environ 2N. De là, nous pouvons faire un JOIN puis LIMIT.

SELECT * 
FROM
(
       SELECT *
        FROM
        (
            SELECT *
            FROM users
            WHERE users.ownership = 1 OR users.stamp = 1
        ) as U
        WHERE 
            Rand() <= $limitCount * $factor / (SELECT count(*) FROM users WHERE users.ownership = 1 OR users.stamp = 1)
) as randUser
JOIN profiles
ON randUser.id = profiles.memberid AND profiles.photo != ''
LIMIT $limitCount

Sur une grande table, cette requête surperformera une requête ORDER by Rand () normale.

J'espère que cela t'aides!

0
lawrenceshen