SELECT id, name FROM person WHERE status=2 ORDER BY id desc;
ID降順でソートしたいとき、このようなSQLになります。
では、IDが3のデータを必ず先頭にして、その後はID降順のソートにするときはどのようなSQLになるでしょうか?
SQL文は
SELECT id, name FROM person WHERE status=2 ORDER BY id=3 desc, id desc;
となります。
これをLaravelのクエリビルダで書くにはどうしたらいいでしょうか?
ここではオープンソースの梅子CMSで使っているプログラムを例に紹介します。
LaravelクエリビルダのよくあるSQL
梅子CMSのソースの一部、
app/Models/Article.phpのgetPublishList
は以下のようになっています。
$table = DB::table($this->table);
$data = $table
->leftjoin('users as user', 'user.id', '=', 'article.user_id')
->leftjoin('save_file as s_file', 's_file.id', '=', 'article.icatch')
->select($cols)
->where('status', config('umekoset.status_publish'))
->where('article.publish_at', '<=', $now)
->orderBy('article.publish_at', 'desc')
->orderBy('article.id', 'asc')
->limit($num)
->get();
return $data;
こんな感じになっています。
通常、LaravelでORDER BYを使うときはorderByのクエリビルダを使います。
では、ORDER文を少し変更して「id=3が先頭に来る」ように書くとどうなるでしょうか。
エラーが起きるLaravelクエリビルダのSQL
id=3が先頭にくるようにすると、こんな書き方をしたくなると思います。
$table = DB::table($this->table);
$data = $table
->leftjoin('users as user', 'user.id', '=', 'article.user_id')
->leftjoin('save_file as s_file', 's_file.id', '=', 'article.icatch')
->select($cols)
->where('article.publish_at', '<=', $now)
->orderBy('article.id=3', 'desc')
->limit($num)
->get();
return $data;
※便宜上、ステータス条件のwhereを外しています

これを実行すると、残念ながらSQLエラーになります。

Unknown Column ~~~~ in ‘order clause’
そんなカラム知りませんけど?と言われています。
書き方としてはそこまで間違っていないように思えますが、どうしてエラーになってしまうのでしょうか?
原因:ORDER BYの第一引数まるごとにバックスラッシュをかけるから
エラーとなった部分をよく見てみると、「ORDER BY ‘article’.’id=3’ desc」となっています。

第一引数をバックスラッシュで囲んでしまうため、「id=3」という名前のカラム名を降順、と書かれてしまうのです。
id=3というカラム名は当然ながらありませんので、実行エラーになります。
解決方法:orderByRawでORDER句を直接書けばOK
「orderByRaw」を使ってORDER文を直接書くとエラーが起きなくなります。
$table = DB::table($this->table);
$data = $table
->leftjoin('users as user', 'user.id', '=', 'article.user_id')
->leftjoin('save_file as s_file', 's_file.id', '=', 'article.icatch')
->select($cols)
->where('article.publish_at', '<=', $now)
->orderByRaw('article.id=3 desc, article.id=1 desc, id desc')
->limit($num)
->get();
return $data;

これを実行すると、正常に画面が表示されます。

まとめ
SELECT id, name FROM person WHERE status=2 ORDER BY id=3 desc, id desc;
のような、id=3のデータを必ず先頭にしてその後はID降順のソートにするとき、Laravelでよく使うクエリビルダで書くとSQLエラーになります。
これを防ぐには、orderではなくorderByRawのクエリビルダを使い、ORDER BY句を直接書くことで解決できます。
その他フレームワークでも同じことが起こり得る-ZendFrameworkで直面しました
今回はLaravelで紹介しましたが、その他フレームワークでも同様のことが起こる可能性が高いと思います。
筆者はZendFrameworkで同様の問題に直面しました。
もし「正しい書き方のはずなのにSQLエラーが出る」と思ったら、クエリビルダで自動付与されるバックスラッシュの位置を疑ってみてください。
梅子-Umeko- CMS

梅子-Umeko-はウェブページを管理するCMSアプリケーションです。
Naho Osadaによって開発されました。
梅子は主にブログのような、「更新頻度がそれなりに高い」ものに向いています。