web-dev-qa-db-ja.com

MySQL:ORDER BY Rand()の代替

MySQLのORDER BY Rand()関数のいくつかの代替案について読みましたが、ほとんどの代替案は、単一のランダムな結果が必要な場所にのみ適用されます。

次のような複数のランダムな結果を返すクエリを最適化する方法はありますか?

   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 
57
Tony

2016年更新

このソリューションは、インデックス列を使用すると最適に機能します。

次に、100,000行でマークされたクエリベンチの簡単な例と最適化を示します。

最適化:0ms

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

制限量に関する注意:制限4および4/count(*)。 4は同じ数である必要があります。返す数を変更しても、速度にはそれほど影響しません。制限4と制限1000のベンチマークは同じです。制限10,000で最大600ミリ秒かかりました

結合についての注意:行全体をランダム化するよりも、idのみをランダム化する方が高速です。行全体をメモリにコピーしてからランダム化する必要があるためです。結合は、テーブルクエリを防ぐために、サブクエリにリンクされている任意のテーブルにすることができます。

where句に注意:whereカウントは、ランダム化される結果の量を制限します。結果全体の割合を取り、テーブル全体ではなくソートします。

サブクエリに注意:ジョインを行う場合と追加のwhere句条件をサブクエリとサブサブクエリの両方に配置する必要があります。正確なカウントを取得し、正しいデータを取得します。

最適化されていない:1200ms

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

[〜#〜] pros [〜#〜]

order by Rand()より4倍高速です。このソリューションは、インデックス付き列を持つ任意のテーブルで機能します。

[〜#〜] cons [〜#〜]

複雑なクエリでは少し複雑です。サブクエリで2つのコードベースを維持する必要がある

27
Neoaptt

これは代替手段ですが、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

これは少し複雑ですが、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

最速ではありませんが、一般的なORDER BY Rand()方法よりも高速です:

ORDER BY Rand()は、インデックス付き列のみを検索するために使用する場合、それほど遅くありません。次のように、1つのクエリですべてのIDを取得できます。

_SELECT id
FROM testTable
ORDER BY Rand();
_

ランダムIDのシーケンスを取得し、他のSELECTまたはWHEREパラメーターを使用した別のクエリへの結果をJOIN

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

あなたの場合、それは次のようになります:

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

これは非常に鈍い方法であり、非常に大きなテーブルでは適切ではありませんが、それでも一般的なRand()よりも高速です。ほぼ400000で3000のランダムな行を検索する実行時間は20倍速くなりました。

8
Adlaran

カラムを作成するか、乱数(たとえばphpで生成)を使用してselectに結合し、このカラムで並べ替えます。

1
Pethő Jonatán

今日、これに遭遇し、「DISTINCT」をJOINと一緒に使用しようとしましたが、ランドが各JOINされた行を区別しているため、重複していると思います。私は少し混乱して、次のように機能する解決策を見つけました:

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()は、大きなテーブルでは非常に遅く、

PHPスクリプトで次の回避策を見つけました。

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

次に、PHPでランダムに実行します

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

それから

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

かなり速いようです。..

1
tonio

私が使用しているソリューションは、以下のリンクにも掲載されています。 MySQLのORDER BY Rand()関数を最適化するにはどうすればよいですか?

ユーザーテーブルはプロファイルテーブルよりも大きくなると想定していますが、そうでない場合は1対1のカーディナリティになります。

その場合、プロファイルテーブルに参加する前に、まずユーザーテーブルでランダムに選択します。

最初に選択を行います:

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

次に、このプールから、計算された確率からランダムな行を選択します。テーブルにM行があり、N個のランダム行を選択する場合、ランダム選択の確率はN/Mである必要があります。したがって:

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)

Nは$ limitCountで、Mはテーブルの行数を計算するサブクエリです。ただし、確率に取り組んでいるので、返される行の$ limitCountよりも少ないLESSを持つことができます。したがって、Nに係数を掛けて、ランダムプールサイズを増やす必要があります。

すなわち:

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)

通常、$ factor = 2に設定します。係数を低い値に設定して、ランダムプールサイズをさらに小さくすることができます(例:1.5)。

この時点で、Mサイズのテーブルはすでに約2Nサイズに制限されています。ここから、JOINを実行してから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

大きなテーブルでは、このクエリは通常のORDER by Rand()クエリよりも優れています。

お役に立てれば!

0
lawrenceshen