Laravelで自己結合した関連を双方向で取得したい
解決済
回答 1
投稿
- 評価
- クリップ 0
- VIEW 1,431
Laravel 5.6環境での質問です。
実現したいこと
商品ページでそれに関連する別商品を表示するようなプログラムを実装したいです。
現状の実装
商品テーブルを元に、その商品同士の関連テーブルを定義しています。
下記の通り、Productモデルを関連商品を管理するテーブルで定義しています。
Schema::create('product_relations', function (Blueprint $table) {
$table->unsignedBigInteger('product_id_1')->comment('商品ID_1');
$table->foreign('product_id_1')
->references('id')->on('products')
->onUpdate('cascade')
->onDelete('cascade');
$table->unsignedBigInteger('product_id_2')->comment('商品ID_2');
table->foreign('product_id_2')
->references('id')->on('products')
->onUpdate('cascade')
->onDelete('cascade');
$table->primary(['product_id_1', 'product_id_2']);
$table->timestamps();
});
発生している問題
Productモデルにおいて、product_relationsテーブルから
product_1をキーにproduct_2の商品と、
product_2をキーにproduct_1の商品を合わせた関連を定義したいですのですが、
SQLにてエラーが発生してしまいます。
イメージとしては下記のようなことを行いたいのですが、
モデルやテーブル設計がLaravelのガイドラインと合っていないのか、
関連の定義が誤っているのか、教えていただければ幸いです。
public function relationProducts() {
return $this->belongsToMany(Product::class, 'product_relations', 'product_id_1', 'product_id_2')
->unionAll($this->belongsToMany(Product::class, 'product_relations', 'product_id_2', 'product_id_1'));
}
発生しているエラー
SQLSTATE[21000]: Cardinality violation: 1222 The used SELECT statements have a different number of columns (SQL: (select `products`.*, `product_relations`.`product_id_1` as `pivot_product_id_1`, `product_relations`.`product_id_2` as `pivot_product_id_2` from `products` inner join `product_relations` on `products`.`id` = `product_relations`.`product_id_2` where `product_relations`.`product_id_1` = 1) union all (select * from `products` inner join `product_relations` on `products`.`id` = `product_relations`.`product_id_1` where `product_relations`.`product_id_2` = 1))
-
気になる質問をクリップする
クリップした質問は、後からいつでもマイページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
クリップを取り消します
-
良い質問の評価を上げる
以下のような質問は評価を上げましょう
- 質問内容が明確
- 自分も答えを知りたい
- 質問者以外のユーザにも役立つ
評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。
質問の評価を上げたことを取り消します
-
評価を下げられる数の上限に達しました
評価を下げることができません
- 1日5回まで評価を下げられます
- 1日に1ユーザに対して2回まで評価を下げられます
質問の評価を下げる
teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。
- プログラミングに関係のない質問
- やってほしいことだけを記載した丸投げの質問
- 問題・課題が含まれていない質問
- 意図的に内容が抹消された質問
- 過去に投稿した質問と同じ内容の質問
- 広告と受け取られるような投稿
評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。
質問の評価を下げたことを取り消します
この機能は開放されていません
評価を下げる条件を満たしてません
質問の評価を下げる機能の利用条件
この機能を利用するためには、以下の事項を行う必要があります。
- 質問回答など一定の行動
-
メールアドレスの認証
メールアドレスの認証
-
質問評価に関するヘルプページの閲覧
質問評価に関するヘルプページの閲覧
checkベストアンサー
+1
UNIONする時のselect文のカラム数が合ってないってエラーですね。
laravelのリレーションでやるのは無理がありそうなので中間テーブル作って処理した方が簡単で設計も綺麗なのでは?
UNIONとJOIN繰り返したら遅そうですし
どうしてもUNIONで処理したいならUNIONしたVIEW作ってそれをlaravelで中間テーブルがわりにするのが早そうです
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
15分調べてもわからないことは、teratailで質問しよう!
- ただいまの回答率 88.23%
- 質問をまとめることで、思考を整理して素早く解決
- テンプレート機能で、簡単に質問をまとめられる
2019/08/06 03:32 編集
中間テーブルを作って・・・というのが具体的にどのような実装か
イメージがわかなかったのですが、よろしければもう少し詳細に教えていただけますでしょうか。
そもそも論になりますが、このような相関する自己参照モデルのような機能を実装する場合、
(RDBにおいては)どのような実装がベストプラクティスなのでしょうか。
無理に
・content_id_1 (PK)
・content_id_2 (PK)
のように双方向に作るのではなく、
・content_id (PK)
・relation_id
のようなカラム設計をして、
(content_id, relation_id) => (1, 2)
と
(content_id, relation_id) => (2, 1)
のように、複数行を作って管理した方が(行数は2倍になりますが)
Laravelとも相性が良さそうですしいいのかなとも考えており、引き続き悩んでおります。。。
2019/08/06 11:58
はい、そのような感じが無難かと思います。
現在実行したいSQLを見ると例えば10000件の商品があれば
JOIN(10000件*10000件)
UNION
JOIN(10000件*10000件)
みたいな感じの処理(JOINは結合した上でwhereで削除する)になるはずなので(実際はもっと賢くて短縮しているかもしれないが)
膨大な表を消費するので、普通に2行ずつ入ってしまう方が低コストのような気がします。
あとcascadeされてますが、これって関連商品が連鎖して消えてしまいませんか?
2019/08/06 12:00
参照されている(belongsTo)と参照している(hasMany)をアプリケーション側でマージすれば1行で済むのではないでしょうか(編集画面が面倒くさそうだが、双方消して、都度どちらかを主キーに入れ直せば問題ないように見える)
2019/08/06 13:41
詳細なご回答ありがとうございます。
非常に参考になりました。
確かに下のコメントの通り、アプリケーション側でマージする方法もアリだと思いましたが、
シンプルさを取って、結局上のコメントの通り、2行ずつ処理する方針にしようかと思います。
(整合性をアプリケーションが取らなければならないのは気をつけます。。)
cascadeについては、主テーブルはproductsになると思うので、
productsが消えれば商品同士の関連を自動的に消すという意図で設定しています。
そのため、商品が消えても関連商品は消えないという認識で合ってますよね?
2019/08/06 13:52
商品が消えたら一緒に消えそうにかんじるのですが
https://qiita.com/suin/items/21fe6c5a78c1505b19cb
setnullの方が適切そうなんですがどうでしょうかね
2019/08/06 14:00
上記の例で、念のため商品を削除して確認してみましたが関連する商品レコードは消えないようです。
※関連テーブル上のレコードは消えました。
mikkame様にて回答いただいたリンクのように、
カテゴリと配下商品が1:nで結合している場合は、カテゴリが消えると商品が消えるのはおかしいので、
set nullが適切だと思われます。
とりいそぎ本題の商品同士の関連についてはアドバイス通り解決いたしましたので、
解決済みと致します。