いっきのblog

技術とか色々

Laravelで日ごとに変わるランダムなソートをする

どうも、くずきです。
こないだ、セールスの方からリストの表示をランダムでだせないのかというご要望がありまして、実装してみた話です。

DB側でランダムで出す

基本的にソートはSQL側で行なっているため、なるべくDB側でできないかな〜と思ってたらありました。

SELECT * FROM table ORDER BY RAND();

ORDER BY RAND()をつけるだけで、ランダムになるというすごく便利な代物。
このままだと毎回変わってしまうため、引数にseedをつければランダムだけと固定にできる。

SELECT * FROM table ORDER BY RAND('1234556778');

EloquentでORDER BY RAND()を呼ぶ

Eloquentなら絶対実装してくれてる!!!と思ったら流石ありました。

https://stackoverflow.com/questions/13917558/laravel-eloquent-or-fluent-random-row

Model::inRandomOrder()

バージョンによって書き方は変わるみたいだけど、5.2以上はこれでいけるみたい。
ただ、このままだと先ほども書いたとおり、毎回読み込むたびに変わってしまう。

1ページならいいけど、実際には->paginate()など使ってページネーションしてる場合がほとんだと思う。2ページ目をみにいったのに、新しくランダムになってしまうせいで1ページ目に表示されたものが出てしまうなんてこともあり得るだろう。

なので、こちらも引数をつけて固定にする。

Model::inRandomOrder(1234567)

日ごとにソートさせる

ランダムの値を固定のシードで固定しまったら、テーブルのレコードが増減しない限り固定な訳で・・、日ごとにランダムにさせよう!と思いできた最終コード。

$today = Carbon::today()->timestamp;

$query->table
      ->orderBy('status', 'desc') // 別のソートを優先で行なっている
      ->inRandomOrder($today);

Carbonで今日のタイムスタンプを取りそれを固定にするやり方で、日ごとに変わるソートの出来上がり。ただ、問題点があり

  • 日をまたいだときに新しいランダムになってしまう
  • テーブルのレコードが増減するとランダムの順番も変わってしまう

という、どうしもない2点がある。
一つ目の問題点の解決方法は、サーバー側にアクセスした際にシード値をフロント側に返してその値を使いまわすことで解決はできそうだなぁと思う。

二つ目の問題点は、過去のレコードをランダムにし、新しいレコードを1ページ目などに表示する仕組みにすれば良さそう。新鮮なレコード + 過去のレコードを埋もれさせないって意味ではこちらは近いうちにやっときたい。

感想

プログラム側でゴリらなくても、DB側でいい感じにランダムにできて楽だった。
シード値もフロントと連携すれば解決するし、新鮮さがないリストに使うにはもってこいなんではないだろうか。