Laravel11のバージョンアップで発生したSQLのエラーを解決した
はじめに
Laravel10で動作している基幹システムをLaravel11にバージョンアップしました!
その際下記のようなエラーが発生したので調査して解決したのでなせ発生したのか纏めてみようと思います
local.ERROR: SQLSTATE[42S21]: Column already exists: 1060 Duplicate column name 'id'
以前に業務委託の方に対応してもらったシステムと同じで、大規模なバージョンアップとならないように、定期的なメンテナンスを意識するようになりました
結論から
- EagerLoading時のlimit句の仕様が変わってた[1]
- Laravel11からリレーションで取得するときにlimit条件を追加すると、上記の仕様変更により発行されるクエリが変わる
- さらに、リレーションの定義のところでJoin句を使用することが、Laravel側の想定外の使い方だったけど、システム内ではやってしまっていた
調査と解決の経緯
エラーは下記のリレーション取得時に発生している
public function trouble_shooting_relations_limited()
{
return $this->hasMany(TroubleShootingRelation::class)
->join('trouble_shootings', function ($join) {
$join->on('trouble_shootings.id', '=', 'trouble_shooting_relations.trouble_shooting_id')
->whereNull('trouble_shootings.deleted_at');
})
->orderBy('trouble_shootings.updated_at', 'desc')
->limit(10);
}
上記の処理で発生するクエリを確認する
select
`laravel_table`.*,
@laravel_row := if(
@laravel_group = `store_id`,
@laravel_row + 1,
1
) as `laravel_row`,
@laravel_group := `store_id`
from
(
select
@laravel_row := 0,
@laravel_group := 0
) as `laravel_vars`,
(
select
*
from
`trouble_shooting_relations`
inner join
`trouble_shootings`
on `trouble_shootings`.`id` = `trouble_shooting_relations`.`trouble_shooting_id`
and `trouble_shootings`.`deleted_at` is null
where
`trouble_shooting_relations`.`store_id` in(?)
and `trouble_shooting_relations`.`deleted_at` is null
order by
`trouble_shooting_relations`.`store_id` asc,
`trouble_shootings`.`updated_at` desc
) as `laravel_table`
having `laravel_row` <= 10 order by `laravel_row`;
ん、、!?
なんかlaravel_table
とか@laravel_row
とか@laravel_group
とか見慣れないけど、こんなクエリになるんだっけ🤔
でもこれ、mysqlの変数に代入するようになっているから発生している可能性ありそう
バージョンアップ前と比較する
リリースノートを確認
- Laravel 11.x リリースノート
-
Laravel 11.x アップグレードガイド
リリースノートやアップグレードガイドを確認しても、Eloquentの仕様変更は含まれていなさそうにみえる
Laravelのソースコードで比較
リレーション内で使用しているメソッドを順々に確認したところ、limit句のみ変更されていた
こちらがLaravel10で処理されるlimit句
こちらがLaravel11で処理されるlimit句
処理内容に若干変更が加わっているし、なんなら呼び出されるClassもBuilderからHasOneOrMany変わっている!!
試しにlimit句をコメントアウトして実行してみるとエラーが起きない
ということは、リレーション取得時にlimit句が使えなくなったってことになる???
そんな訳ないか、そうなると、もっとエラーが多発していると思うんだけどな
リレーション処理でjoinってする?
Laravel側で変更があったにせよ、大部分では問題になっていないのでシステム側に問題があるのかもしれない
ということで問題の処理を再掲
return $this->hasMany(TroubleShootingRelation::class)
->join('trouble_shootings', function ($join) {
$join->on('trouble_shootings.id', '=', 'trouble_shooting_relations.trouble_shooting_id')
->whereNull('trouble_shootings.deleted_at');
})
->orderBy('trouble_shootings.updated_at', 'desc')
->limit(10);
そういえばリレーションでjoinする必要あるっけ?
中間テーブルを経由したテーブルの情報を取得したい場合って、HasOneThroughかHasManyThroughを使うんじゃなかったっけ?
このリレーションを使っている部分のソースコードを読んでみても、やりたいことはLaravelで用意している機能でできそうなので、当時の実装担当者がこの機能を知らずに実装したってことなのかな
Laravel側としてもリレーション定義の中でjoin句を書かれることは想定していなかったんじゃないかと思う
処理を書き換えて解決
というわけで、実際にやりたいことを確認しながら、こんな感じに書き換えて無事にエラーは解消されました!
public function trouble_shooting_relations_limited()
{
- return $this->hasMany(TroubleShootingRelation::class)
- ->join('trouble_shootings', function ($join) {
- $join->on('trouble_shootings.id', '=', 'trouble_shooting_relations.trouble_shooting_id')
- ->whereNull('trouble_shootings.deleted_at');
- })
+ return $this->belongsToMany(TroubleShooting::class, TroubleShootingRelation::class, 'store_id', 'trouble_shooting_id')
->orderBy('trouble_shootings.updated_at', 'desc')
->limit(10);
}
最後に
メジャーバージョンを上げると、後方互換のない変更があったりしますが大抵はリリースノートとかに書いてあると思うけど、今回の変更は記載なかったのでLaravelのissueやPRを探したりしてどんな変更が加わったのかわかりますが記載しておいて欲しいなって思いました!
それでも、システムの悪い部分を改善できたのでよかったです!定期的なバージョンアップは大事ですね!
Discussion