Laravelと複合プライマリキー
既存の複合プライマリキーを持つMySQLのテーブルに対する操作で混乱したので備忘録。
複合キーはLaravelでは有名なネタのようですが、幸いにもこれまで対応する機会はありませんでした。
事情によりサロゲートキーは導入できない前提です。
mpywさんよりコメントで便利なトレイトを教えていただいたので、公開半日もせず大幅に改稿しました。
HasCompositePrimaryKey トレイトの導入
mpywさんより教えていただいたトレイトです。
https://github.com/mopo922/LaravelTreats/blob/master/src/Model/Traits/HasCompositePrimaryKey.php
含まれている LaravelTreats をまるごとインストールします。
$ composer require mopo922/laravel-treats
Model にトレイトを挿入します。
class KeywordItem extends Model
{
use \LaravelTreats\Model\Traits\HasCompositePrimaryKey;
/**
* モデル主キー
* @var int
*/
protected $primaryKey = [ 'keyword_id', 'item_id' ];
/**
* 複数代入する属性 (データの代入が必須)
*
* @var array
*/
protected $fillable = [ 'keyword_id', 'item_id' ];
}
これで動いてくれたら嬉しいコードで問題なく挿入・更新ができるようになりました。
// new OK, update OK
$new = KeywordItem::firstOrNew([ 'keyword_id' => $keyword_id, 'item_id' => $item_id ]);
$new->status = $status;
$new->save();
わーい!!
追記。
https://github.com/mopo922/LaravelTreats/issues
現在、chunkまわりで不具合もあるようなので利用時は気をつけてください。
HasCompositePrimaryKey トレイト導入前の解決法
以下は、トレイト導入前の投稿となります。
Modelの定義は以下の通り。
class KeywordItem extends Model
{
/**
* モデル主キー
* @var int
*/
protected $primaryKey = [ 'keyword_id', 'item_id' ];
/**
* increment無効化
*/
public $incrementing = false;
/**
* 複数代入する属性 (データの代入が必須)
*
* @var array
*/
protected $fillable = [ 'keyword_id', 'item_id' ];
}
新規、更新ともに成功。
// new OK, update OK
$new = KeywordItem::where([ 'keyword_id' => $keyword_id, 'item_id' => $item_id ]);
if ($new->count() === 0) {
$new = new KeywordItem([ 'keyword_id' => $keyword_id, 'item_id' => $item_id ]);
$new->status = $status;
$new->save();
}
else {
$new->update(['status' => $status]);
}
新規はうまくいくものの、更新した値が反映されない。
// new OK, update NoUpdate
$new = KeywordItem::where([ 'keyword_id' => $keyword_id, 'item_id' => $item_id ])->first();
if (is_null($new)) {
$new = new KeywordItem([ 'keyword_id' => $keyword_id, 'item_id' => $item_id ]);
$new->status = $status;
$new->save();
}
else {
$new->update(['status' => $status]);
}
新規はうまくいくものの、更新で例外エラーが発生。
// new OK, update Error
$new = KeywordItem::firstOrNew([ 'keyword_id' => $keyword_id, 'item_id' => $item_id ]);
$new->status = $status;
$new->save();
新規、更新ともに例外エラーが発生。ただし新規の例外はINSERT後となる。
// new Error(but after insert), update Error
$new = KeywordItem::firstOrCreate([ 'keyword_id' => $keyword_id, 'item_id' => $item_id ]);
$new->status = $status;
$new->save();
新規は挿入されないし、更新も値が反映されない。
// new NotInsert, update NoUpdate
KeywordItem::updateOrCreate(
['keyword_id' => $keyword_id, 'item_id' => $item_id],
['status' => $status]
);
以上です。
なんだか、直接クエリ書く方がシンプルな気がしてきます。