Skip to content

Instantly share code, notes, and snippets.

@esehara
Last active April 2, 2020 10:53
Show Gist options
  • Save esehara/5925390 to your computer and use it in GitHub Desktop.
Save esehara/5925390 to your computer and use it in GitHub Desktop.
ORDER BY RAND() 使うな ---- Original by: “Do not use ORDER BY RAND()” or “How to get random rows from table?” http://www.titov.net/2005/09/21/do-not-use-order-by-rand-or-how-to-get-random-rows-from-table/

SELECT quote FROM quotes ORDER BY RAND() LIMIT 1

テーブルからランダムの行を手に入れる為のSQL文だが、これはとても悪いアイデアだ。貴方が欲しいものがどんなものであれ、巨大なテーブルを持っていて、たった50-100行を取り出すであったとしてもね。

貴方がこのクエリーを走らせるたびにどんなことが起きているのだろう? 10000行を持つテーブルの上で、このクエリーを走らせてみよう。すると、SQLは10000の乱数を発行し、一番小さい数を調べて、その行を与える。乱数を作ることは、比較的には大変な操作であり、それらの一番小さな数をを見つけてくること自体はとてもはやい(例えば、貴方が LIMIT 10にしていたら、それらで小さい順に10個の数を取ってくるーーもし、このテキストの引用が遅いなら、それはたぶんできるだけ早くするためにサイズを何らかの形であわせているからだ。たぶん、テンポラリーテーブルを作る必要があるから、とか)。

で、どうやってこんなオーバーヘッドを起こさずに、ランダムな行を取ってくればいいんだろう?簡単に置き換えることはそう簡単ではない。次のようなことは、できる。

SELECT COUNT(*) AS cnt FROM quotes

プログラミングの中で、0からcnt-1の間でランダムに数を作って、クエリーを走らせてみよう。

SELECT quote FROM quotes LIMIT $generated_number, 1

そう。これは二つのクエリーを走らせている。しかし、これは最初のよりも「すげえ」はやい。この選択は、貴方が一つのランダムの行をとってくるだけであるならば、良い。もしもっと行を取ってくるとするならば、貴方はまだこのトリックは使える。ただ、ランダムに数を生成してcntからX(Xは必要な行の数)をとってきて、次のようなクエリーに変更すればいい。

SELECT quote FROM quotes LIMIT $generated_number, X

しかし、貴方に与えられるsusequent行は、ランダムな場所から始まる。もしこの選択を選ばないなら、まだ別のアプローチがある。殆どの場合、ユニークである実数のフィールドをテーブルに持ち、そして1からどんどん成長させて、次のように書けばよい。

SELECT MAX(id) AS maxid FROM quotes

1からXの間でランダムな数Xを作り、それらの数を','の文字列でつないであげて、次のキューを走らせる。

SELECT quote FROM quotes WHERE id IN ($idlist)

そう。貴方は恐らく消えたテーブルがいくつかあって、このクエリーは、貴方が必要とするよりも少ないクエリーを返すだろう。とはいえ、貴方は10回ランダムのidを生成して、次のようなクエリーに変更が出来る。

SELECT quote FROM quotes WHERE id IN ($list_with_10_times_more_ids_than_x) LIMIT $x

今、貴方がテーブルからあまりにも多くの行が消えていないんだったら、0の近くで、100このランダムの数を使って、そのリストに10のidが存在しているかどうかみつけるのは難しいし、貴方はコードの行をプログラムのなかで 増やすことになる。もし貴方が結果として$x行を持っているかどうかをチェックして、もしそうでなければ、きっとORDER BY RANDのコードとにてくるだろう。貴方は同様に貴方が必要としている以上の100回以上のidを作り、そして最初に紹介したオーバーヘッドは$x回よりも大きな数をつくることになる。(例えば貴方が一万行の中から5行ランダムにとってくる必要があるなら、一万よりも500の数を作るほうがいいというわけだ)。そして、クエリーのパースがちょっと遅くなったら、テーブルからスキャンすることは遅くならない。SQLサーバーは、リストから対応するidの行をすぐにきっと行を探してくるのを止めるだろう。

@iotkaden20230322
Copy link

iotkaden20230322 commented Apr 2, 2020

特定の生年月日の人間だけを抽出して、その後ランダムに10人を抽選する場合
WHEREでUK指定してもそのUKが該当する誕生日ではない可能性があるため、そういったときには使えませんね

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment