ChupiPichu
@ChupiPichu (Chupi)

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

【Laravel 8】既存テーブルにある重複の値のみを抽出して新しいテーブルの追加するためのリレーションについて

解決したいこと

会員制掲示板のユーザー登録時にgroup_idとgroupの情報が重複しますので、手探りでテーブルを追加しました。今のところは問題ないのですが、リレーションの作り方が妥当かどうか不安ですが、ご指摘などありましたらいただけますでしょうか。

該当するソースコード

Group.php(今回追加となったGroupのテーブルのモデル)

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use App\Models\User;

class Group extends Model
{
    use HasFactory;
    protected $fillable = [
        'name',
        'group',
        'group_id',
    ];
    public function post() {
        return $this->belongsTo('App\Models\Post');
    }
    public function comment()
    {
        return $this->belongsTo('App\Models\Comment');
    }
    public function user() {
        return $this->hasMany('App\Models\User');
    }
}

User.php

中略

    public function belongs() {
        return $this->belongsTo('App\Models\Belong');
    }

}


RegisterController.php

    protected function create(array $data)
    {
        // userテーブルのデータ
        $attr = [
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => Hash::make($data['password']),
            'group' => $data['group'],
            'group_id' => $data['group_id']
        ];

        $groupattr = [
            'name' => $data['name'],
            'group' => $data['group'],
            'group_id' => $data['group_id']
        ];

        $user = User::create($attr);
        $group = Group::create($groupattr);
    }

}
register.blade.php

   <div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">{{ __('アカウント作成') }}</div>

                <div class="card-body">
                    <form method="POST" action="{{ route('register') }}" enctype="multipart/form-data">
                        @csrf

                        <div class="row mb-3">
                            <label for="name" class="col-md-4 col-form-label text-md-right">{{ __('Name') }}</label>

                            <div class="col-md-6">
                                <input id="name" type="text" class="form-control @error('name') is-invalid @enderror" name="name" value="{{ old('name') }}" required autocomplete="name" autofocus>

                                @error('name')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>

                        <div class="row mb-3">
                            <label for="goroup_id" class="col-md-4 col-form-label text-md-right">{{ __('グループID') }}</label>

                            <div class="col-md-6">
                                <input id="goroup_id" type="text" class="form-control @error('goroup_id') is-invalid @enderror" name="goroup_id" value="{{ old('goroup_id') }}" required autocomplete="goroup_id" autofocus>

                                @error('goroup_id')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>

                        <div class="row mb-3">
                            <label for="goroup" class="col-md-4 col-form-label text-md-right">{{ __('グループ名') }}</label>

                            <div class="col-md-6">
                                <input id="goroup" type="text" class="form-control @error('goroup') is-invalid @enderror" name="goroup" value="{{ old('goroup') }}" required autocomplete="goroup" autofocus>

                                @error('goroup')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>

                        <div class="row mb-3">
                            <label for="email" class="col-md-4 col-form-label text-md-right">{{ __('E-Mail アドレス') }}</label>

                            <div class="col-md-6">
                                <input id="email" type="email" class="form-control @error('email') is-invalid @enderror" name="email" value="{{ old('email') }}" required autocomplete="email">

                                @error('email')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>


                        <div class="row mb-3">
                            <label for="password" class="col-md-4 col-form-label text-md-right">{{ __('Password') }}</label>

                            <div class="col-md-6">
                                <input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" required autocomplete="new-password">

                                @error('password')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>

                        <div class="row mb-3">
                            <label for="password-confirm" class="col-md-4 col-form-label text-md-right">{{ __('Confirm Password') }}</label>

                            <div class="col-md-6">
                                <input id="password-confirm" type="password" class="form-control" name="password_confirmation" required autocomplete="new-password">
                            </div>
                        </div>

                        <div class="row mb-0">
                            <div class="col-md-6 offset-md-4">
                                <button type="submit" class="btn btn-primary">
                                    {{ __('Register') }}
                                </button>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div

下記のER図に今回新たにGroupテーブルを追加することになります。
QuickDBD-export.png

0

1Answer

GroupとUserはどのような関係でしょうか、中間テーブルなどはありますか?
DBのテーブルや作成時のマイグレーションファイルがあれば見せていただければと思います。
また、RegisterController.phpのcreateメソッドを叩く前のviewのformはどのようになっていますでしょうか?

下記気づいた点がありますのでご確認ください。
・Group.phpのクラス名がBelongになってますが大丈夫でしょうか?

group_idとgroupの情報が重複しますので

・上記はテーブル設計としてあまり良くないかと思います。group_idが主キーでしたらUserはgroup_idさえ持っていたらリレーションでGroupの情報をまとめて取得できるかと思います(テーブル設計が現時点で不明なため断言できませんが)。

0Like

Comments

  1. @ChupiPichu

    Questioner

    コメントありがとうございます。
    ➀中間テーブルはありませんが、ER図は上記に掲載しております。今回はその図に新たにgroupのテーブルを追加しました。いかがでしょうか。
    ②RegisterController.phpでつながっているfromは上記に追加いたしました。
    ③Group.phpのクラス名は誤字で修正しました。
    ④テーブルを追加する経緯としてはgroup_idもgroupも例えば、group_idは1や2を重複して使えたり、グループ名も(group)Aグループなど重複してつけれることからテーブルを分けた方がよいと指摘されたためですが、この場合、group_idが主キーになっていますでしょうか。テーブル設計に関してアドバイスなどいただけたら幸いです。
  2. >➀中間テーブルはありませんが、ER図は上記に掲載しております。
    >今回はその図に新たにgroupのテーブルを追加しました。いかがでしょうか。

    いえ、グループのテーブルを頂けた方が話が早いかと思われます。
    ER図を確認しましたが、Usersだけではなく、
    PostsとCommentsにもグループがあるのですね。
    今回は、Usersだけにフォーカスしますね。


    >②RegisterController.phpでつながっているfromは上記に追加いたしました。

    ユーザー登録画面でユーザーと、
    そのユーザーのグループIDとグループネームが登録できるのですね。
    承知しました。

    >group_idは1や2を重複して使えたり、
    >グループ名も(group)Aグループなど重複してつけれることからテーブルを分けた方がよいと指摘されたためですが、


    上記の意味が分からなかったのですが、
    テーブルでいうと下記みたいな感じですか?

    groupid groupname?
    1 null
    2 null
    null Aグループ
    1 Aグループ ←これもありえますか?

    そもそも、仕様としてグループはどのような機能になりますか?
    例えば、グループ1ボタンをクリックすると、
    グループ1に属するユーザー一覧が表示されるなどですか?
    それとも、特に機能はなく、リンクの無いタグの様に表示されるだけなど、
    @ChupiPichuさんが達成したい要件によってテーブル設計を変更する必要が出てくると思います。

    前者であればgroupテーブル及び場合によっては中間テーブルが必要になりますし、
    後者であればgroupテーブルはいらないかと思われます。

    >この場合、group_idが主キーになっていますでしょうか。

    上記の通りであればgroupidは主キーではありません。
    おそらく、ER図から察するにオートインクリメントで
    1カラム目にidがあったりするんじゃないでしょうか?
    そちらが主キーになります。
    ちなみに主キーの説明は下記になります。
    https://wa3.i-3-i.info/word1991.html

    あと、元々書かれていたbelongsToManyとは
    中間テーブルが存在する場合のリレーションの記述になります。
    もしかすると中間テーブルがなくても値が取れたりするかもしれませんが、
    検証していないのでわかりません。

    下記一読ください。

    https://qiita.com/mtakehara21/items/3cef9d12869d162e1ce9#%E5%A4%9A%E5%AF%BE%E5%A4%9A%E3%81%AE%E9%96%A2%E4%BF%82

    その他不明点があれば聞いてください。
  3. @ChupiPichu

    Questioner

    ご回答ありがとうございます。助かります。
    ➀グループのテーブルも追加したER図を上記にアップロードしました。
    ②ユーザー登録時、groupidもgroupも入力必須となります。
    なので、下記のように自由に決められます。
    1 Aグループ
    1 Aグループ
    1 Bグループ
    2 Cグループ
    2 Dグループ

    仕様としてグループの機能は例えば、1 Aグループと登録したユーザーは1 Aグループの投稿しか見れません。投稿一覧も1 Aグループの投稿しかありません。
    ボタンを設置しておらず、「全員一覧」を押すとそれぞれのグループが自分たちのグループの投稿しか見れない仕様です。
    この場合、groupテーブルは必要でしょうか。
    もし変更する必要がある場合、テーブル設計にどう変更を加えればよろしいかご知見をいただけますでしょうか。

    ③主キーについて教えて頂き、ありがとうございます。しっかり勉強させていただきます。

    ④belongsToManyについてのご指摘、ありがとうございます。参考文献で考えて一人のユーザーは一つのグループやグループ名にしか所属しないので、hasOneに修正しましたが、いかがでしょうか。上記に反映しております。

    ⑤追記文献もしっかり勉強させていただきます。

  4. >➀グループのテーブルも追加したER図を上記にアップロードしました。
    拝見しました。

    >②ユーザー登録時、groupidもgroupも入力必須となります。
    >(略)
    >この場合、groupテーブルは必要でしょうか。

    groupテーブルは必要といいますか有った方がいいと思います。
    正規化というデータベースからカラムを切り出す作業がありますので確認してみてください。

    https://qiita.com/mochichoco/items/2904384b2856db2bf46c

    また、現在のつくりを見てみると、
    ユーザー登録画面でユーザーとグループを登録すると思うのですが、
    RegisterController.phpのcreateメソッドにおいて、Groupのインスタンスをcreateしてますよね?
    これはGroupテーブルが存在することが前提ではないですか?
    また現状のコードを確認する限り、ユーザーは既に存在するグループから選べないような気がしますが仕様でしょうか?

    >もし変更する必要がある場合、テーブル設計にどう変更を加えればよろしいかご知見をいただけますでしょうか。

    現状のテーブルに関してですが、
    まず、Groupテーブルでgroup〇〇カラムみたいなのはやめたほうがいいと思います。
    理由はGroupテーブルにあるため、見れば分かるので必要がないです。

    下記を参照ください。

    https://qiita.com/tatsuya_1995/items/4b706fc40fe2f300bbc0#%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E5%90%8D%E3%81%8B%E3%82%89%E5%88%86%E3%81%8B%E3%82%8B%E3%81%93%E3%81%A8%E3%81%AF%E6%9B%B8%E3%81%8B%E3%81%AA%E3%81%84

    また、Groupテーブルのgroupは何でしょうか?
    別にnameもあるのですか?

    仕様でgroupidもgroupも入力必須とのことですが、
    要点が多いため、一旦group(name?)のみの前提にさせてください。

    下記自分だったらこうするってのを書きます。

    groupテーブル

    id
    name

    userテーブル

    id
    group_id
    以下略
    (削除や更新を考えて外部キーも設定したほうがいいかもしれません)
    https://qiita.com/SLEAZOIDS/items/d6fb9c2d131c3fdd1387

    ユーザーは一つのグループに所属することができる
    グループは複数のユーザーが所属しているという仕様だと思うので、
    Group.php(モデル)でUserモデルに対して、hasMany
    User.phpでGroupモデルに対して、belongsToで良いかと思われます。

    現状リレーションにおいてが問題が出ていないとのことですが、
    おそらくそれはリレーションを使っていないからだと思います。

    GroupかUserでインスタンスを作成して、
    下記の様にそれぞれのリレーションを呼んででみていただければと。

    $group = Group::find(1);
    dd($group->users);//このusresです

    https://laraweb.net/practice/4369/
  5. @ChupiPichu

    Questioner

    コメントありがとうございます。
    ➀紹介してくださった文献(https://qiita.com/mochichoco/items/2904384b2856db2bf46c
    を拝見したことがあります。「カラムを切り出す作業」のつもりでGroupテーブルを作ったのですが、どうも間違っているのであれば、ほかにどう手を加えたらカラムを切り出すことができますでしょうか。
    ②おっしゃるようにユーザー登録画面でユーザーとグループを登録し、RegisterController.phpのcreateメソッドにおいて、Groupのインスタンスをcreateしております。これはGroupテーブルを作ったから追加したものですが、Groupテーブルの必要性やもし中間テーブルが必要な場合の作り方などきちんと理解していないまま作ってしまったものです。
    ③ユーザーが登録時に例えば、グループIDを「1」に、グループ名を「Aグループ」にした場合、その後変更できない仕様です。登録した後は「Aグループ」の投稿一覧しか閲覧できないようになっております。
    ④文献を拝見しました。
    Groupテーブルのカラム名の変更ですが、groupidの場合、例えば、「1」や「2」などユーザー登録時に連続して使えるため、どう変えたら妥当でしょうか。グループ名を意味するgroupカラムをnameなどにしたほうがよいということですが、Groupテーブルにすでにユーザーのnameのカラムが入っているため、テーブル同士をどうリレーションさせたらよろしいでしょうか。
    例えばかきのようになりますでしょうか。

    groupテーブル

    id
    group_id
    name

    userテーブル

    id
    group_id
    group_name

    group_idかgroup_nameのどちらか一つにしない理由はどちらも内容が重複する可能性があるためです。

    ⑤外部キーについて拝見しましたが、まだ理解が追い付いておりません。ロリポップのサーバーですが、userテーブルとgroupテーブルそれぞれにmysqlのクエリで入力して実行するイメージでしょうか。

    ⑥Group.phpでhasManyに、User.phpでbelongsToに変更し、上記にも反映しておきました。つまり、この場合、Group.phpが親テーブルで、User.phpが子テーブルという理解で合っていますでしょうか。

    ⑦おっしゃるようにリレーションを作動できていないと思います。インスタンスということはControllerを作って、下記コードを試すということですね。

    $group = Group::find(1);
    dd($group->users);//このusresです
  6. >①
    下記の重複と、リレーションの件が分からないと何とも言えません。
    あとできればuserとgroupのそれぞれのカラムが何を指しているのかが分かればいいかと思いました。

    >②
    承知しました

    >Groupテーブルにすでにユーザーのnameのカラムが入っているため、

    Group.nameはユーザー名だったのですね。
    でしたら、nameではなくuser_nameにした方がいいですね。
    また、そもそもgroupテーブルにユーザー名は必要でしょうか?
    groupsからuserを取得できますよね?

    >Groupテーブルにすでにユーザーのnameのカラムが入っているため、
    >テーブル同士をどうリレーションさせたらよろしいでしょうか。

    前回回答したように、hasmanyとbelongstoで良いかと思います。

    >テーブル同士をどうリレーションさせたら

    リレーションさせるとはモデルにおけるlaravelのリレーション設定のことではないですか?何を指してますか?

    >group_idかgroup_nameのどちらか一つにしない理由はどちらも内容が重複する可能性があるためです。

    何と何が重複しますか?
    下記の場合1と2は重複しているという意味ですか?

    1 1 Aグループ
    2 1 Aグループ
    3 1 Bグループ
    4 2 Cグループ
    5 2 Dグループ

    >⑤外部キーについて拝見しましたが、まだ理解が追い付いておりません。ロリポップのサーバーですが、userテーブルとgroupテーブルそれぞれにmysqlのクエリで入力して実行するイメージでしょうか。

    laravelのマイグレーションで設定できます。
    もちろんSQLでも行えます。
    https://readouble.com/laravel/8.x/ja/migrations.html
    外部キー制約の項目


    その認識で大丈夫だと思います。


    そうでうすね、作らなくても既存のものでもいいですし、
    コントローラでもモデルでもOKです。
    tinkerってのもあります。
    https://qiita.com/juve_534/items/96dc6e7e0652dced1428
  7. @ChupiPichu

    Questioner

    コメントありがとうございます。
    ➀下記で重複とリレーションの件を説明してみましたが、答えになりましたでしょうか。

    ②groupテーブルのほうにユーザー名を入れたのは自分自身がわからなくならないようにするためですが、テーブル設計的に良くない場合、削除しておきます。カラムを残してもよい場合、user_nameに変更しておきます。

    ③hasmanyとbelongstoをそれぞれのモデルに追加することで、groupsからuserを取得できるということですね。ありがとうございます。「>テーブル同士をどうリレーションさせたら」と述べたのはhasmanyとbelongstoをそれぞれのモデルに追加することで、groupsからuserを取得できるということをリレーションと表現してしまいました。わかりにくくて申し訳ないです。

    ④おっしゃるように下記の場合の1と2は重複しているという意味です。

    1 1 Aグループ
    2 1 Aグループ
    3 1 Bグループ
    4 2 Cグループ
    5 2 Dグループ

    ⑤文献を拝見しました。
    マイグレーションのほうは若干親近感を感じますので、助かります。
    つまり、この場合は以下のようにマイグレーションファイルに追加してマイグレーションすることになりますでしょうか。

    ・Usersテーブルのマイグレーションファイル
    Schema::table('group', function (Blueprint $table) {
    $table->foreignId('user_id')->constrained('users');
    });

    ・Groupテーブルのマイグレーションファイル
    Schema::table('users', function (Blueprint $table) {
    $table->foreignId('group_id')->constrained('group');
    });

    ⑥ご確認いただき、ありがとうございます。


    ⑦tinkerも試してみます。

  8. そういう理由があったのですね、


    承知しました。


    この重複を回避したいのであれば、
    DBに複合主キー制約または複合ユニーク制約を貼ればいいと思います。
    登録の際、重複した時点で登録できなくすることができます。

    https://qiita.com/moai000/items/ca74bced43b2700c6134
    https://loop-never-ends.com/laravel-unique-columns/


    【MySQL】複合主キー制約と複合ユニーク制約の違い
    https://honobonolab.com/mysql-primary-unique/


    具体的な記述に関しましては
    こちらで検証できないので、回答出来かねます。
    実際にマイグレーションを実施して、
    その後DBに接続して外部キーが張られているかと、
    実際に想定通りの挙動になるかを確認していただければと思います。

    やり取りと議題が多くなりまた、私自身の理解が正しいかわからないため、
    的外れな回答になってたらすみません。
  9. @ChupiPichu

    Questioner

    コメントありがとうございます。
    ➀DBに複合主キー制約または複合ユニーク制約については初耳ですので、例えば下記文献のようにマイグレーションファイルに追加することで合っていますでしょうか。
    https://qiita.com/wrbss/items/7245103a5fef88cbdde9
    $table->foreign('group_id')->references('id')->on('users');
    $table->foreign('group_id')->references('id')->on('users');
    $table->unique(['name', 'group_id']);
    ②外部キーについてのマイグレーションファイルが想定通りの挙動になるかどうかについて、例えばこちらがユーザー登録時にきちんとユーザーテーブルにもグループテーブルにも登録されていたら問題ないと判断してよいという理解で合っていますでしょうか。

    回答してくださったことは大変的確な内容だと思います。様々教えて頂き、本当にありがとうございます。

Your answer might help someone💌