Laravel
laravel5
laravel5.7

Laravel5.7における複合プライマリキーを持つテーブルへの挿入と更新


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 にトレイトを挿入します。


KeywordItem.php

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の定義は以下の通り。


KeywordItem.php

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]
);

以上です。

なんだか、直接クエリ書く方がシンプルな気がしてきます。