質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
Laravel

LaravelとはTaylor Otwellによって開発された、オープンソースなPHPフレームワークです。Laravelはシンプルで表現的なシンタックスを持ち合わせており、ウェブアプリケーション開発の手助けをしてくれます。

PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

Eloquent

Eloquentとは、PHPフレームワークのLaravelに最初から含まれているORM(Object-relational mapping:オブジェクト関係マッピング)です。

Q&A

3回答

3607閲覧

Laravel ランダム順で取得したレコードを1ページずつで表示したいが、正しく取得できない

Rint

総合スコア14

Laravel

LaravelとはTaylor Otwellによって開発された、オープンソースなPHPフレームワークです。Laravelはシンプルで表現的なシンタックスを持ち合わせており、ウェブアプリケーション開発の手助けをしてくれます。

PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

Eloquent

Eloquentとは、PHPフレームワークのLaravelに最初から含まれているORM(Object-relational mapping:オブジェクト関係マッピング)です。

0グッド

2クリップ

投稿2019/06/22 12:49

やりたいこと

Laravelで自分で作る英単語帳アプリを作っており、
保存されたレコードの指定範囲からクイズを出題する機能を追加しようとしています。

考えているやり方

・テスト形式選択画面のViewで、テスト範囲を「自分が保存した単語」or「他人が保存した単語のうち、良いねしたもの」から選んでFormでコントローラに送信
・コントローラで、
①Formデータに基づき、Viewに渡すレコードを取得
②(1ページ目の場合)レコードをランダム順にし、1ページ1レコードのページネーションをつけてViewに渡す
(2ページ目以降の場合)1ページ目のViewから渡された順番の通りとし、1ページ1レコードのページネーションをつけてViewに渡す
・Viewでは入力欄だけつくる(答えをForm送信はしない)。ボタンを押すと答えが表示されるように。
コントローラから受け取った順序をページネーションリンクにappendsメソッドで追加して渡す

問題点

あるユーザの「他人が保存した単語のうち、良いねしたもの」が①②③④の4つあるとします。
テスト範囲を「他人が保存した単語のうち、良いねしたもの」にすると、
①②②③のような4ページになることが度々あります。(一つの組み合わせに同じカードが2つ存在。含まれないカードが1つある)
※毎回ではありません。正しく①②③④の4ページになることもあります。

解決方法についてアドバイスをいただきたく、よろしくお願いいたします。

現在のコード

TestController.php

php

1 public function test(Request $request, $id) { 2 $user = User::find($id); 3 $scope = $request->input('scope'); //テスト範囲:2択 4 $order = $request->input('order');//1ページ目から渡された順序を$orderとする 5 6 if ($scope == 'my_cards') { 7 $cards = $user->cards(); 8 } else { 9 $cards = $user->good_cards(); 10 } 11 12 /*$orderが空なら(=1ページ目なら)ランダムが並び順。 13     $orderの中身があれば(=2ページ目以降なら)$orderが並び順。*/ 14 if ($order == null) { 15 $count = $cards->count(); 16 $numbers = range(1, $count); 17 shuffle($numbers); 18 $pages = null; 19 foreach ($numbers as $number) { 20 $pages .= $number; 21 }; 22 } else { 23 $pages = $order; 24 }; 25 26 $cards = $cards->inRandomOrder($pages)->paginate(1); 27 28 return view('test.test', [ 29 'cards' => $cards, 30 'order' => $pages, 31 'scope' => $scope, 32 ]); 33 }

test.blade.php

PHP

1@extends('commons.app') 2@section('content') 3<div class="container"> 4 <ul class="list-unstyled"> 5 @foreach ($cards as $card) 6 <li> 7 <div class="row"> 8 <div class="col-6"> 9 <div class="card"> 10 <div class="card-body"> 11 <p class="card-title">英語:</p> 12 <p class="card-text">{{ $card->english }}</p> 13 </div> 14 </div> 15 </div> 16 <div class="col-6"> 17 <div class="card"> 18 <div class="card-body"> 19 <p class="card-title">日本語:</p> 20 <textarea rows="4" class="form-control"></textarea> 21 </div> 22 </div> 23 <div class="hidden_box"> 24 <label for="answer" class="btn btn-dark btn-block">答えを見る</label> 25 <input type="checkbox" id="answer"> 26 <div class="hidden_show"> 27 <div class="card"> 28 <div class="card-body"> 29 <p class="card-title">答え:</p> 30 <p class="card-text">{{ $card->japanese }}</p> 31 </div> 32 </div> 33 </div> 34 </div> 35 </div> 36 </div> 37 </li> 38 @endforeach 39 </ul> 40 {{ $cards->appends(['scope' => $scope, 'order' => $order])->render('pagination::bootstrap-4') }} 41</div> 42@endsection

web.php

php

1Route::group(['middleware' => 'auth'], function () { 2 Route::group(['prefix' => 'users/{id}'], function (){ 3 Route::get('test', 'TestController@test')->name('users.test'); 4 }); 5});

User.php

php

1 public function cards() 2 { 3 return $this->hasMany(Card::class); 4 } 5 6 public function good_cards() 7 { 8 return $this->belongsToMany(Card::class, 'good', 'good_user_id', 'card_id')->withTimestamps(); 9 }

確認したこと

・テスト範囲$scopeを「自分が保存した単語」にすると毎回問題なく全カードが1ページずつ表示されます。
・Viewに渡された良いねしたカード$user->good_cards()の中身は毎回正しく①②③④全カードの並び替えになっています。ダブっていません。
$cards->inRandomOrder($pages)->paginate(4);に変えてみて確認)
$pagesの中身は正しく1~4をランダムに並び替えたものになっています。(Viewに渡して確認)

補足

別の問題かもしれませんが、当初は$cards = $cards->inRandomOrder($pages)->paginate(1);$pagesの中身を1234と固定にしていました。すると順番が毎回同じになりました。なので1ページ目の場合はシャッフルしたものを$pagesに入れるようにしています。

環境

Laravel 5.5

よろしくお願いいたします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Calmar

2019/07/02 11:07

本質問ですが、 $cards->inRandomOrder($pages)->paginate(4);の場合には、重複無く正しいデータが取得されるが、 $cards->inRandomOrder($pages)->paginate(1);にした場合、データが重複してしまう。 と言う内容で間違いないでしょうか?
Rint

2019/07/09 14:36

上記の通りで間違いありません。 ご検討いただきありがとうございます。
tetsunosuke

2019/07/23 04:02

たとえば、こちらのような方法で、実際に実行しているSQLをまずは確認してみて、同じクエリを単純にMySQL側で実行してみてはいかがでしょうか? https://qiita.com/nao_tuboyaki/items/477aea83c487c98d9ef0 実はレコードの中にだぶっているものがあるのかもしれないです。(論理削除しているデータとか)
guest

回答3

0

データを取得する際にgroupByでまとめてしまうのはいかがでしょうか?
または、distinctのように重複行をまとめるものあるみたいなので、それでもいけるかもしれないです!
https://readouble.com/laravel/5.5/ja/queries.html

投稿2019/06/22 15:22

fumito_94

総合スコア679

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Rint

2019/06/23 05:55

アドバイスありがとうございます。 $cards = $user->good_cards()->distinct();は重複が解消できず、groupByは$cards = $user->good_cards()の後につけても$cards = $cards->groupBy('cards.id')->inRandomOrder($pages)->paginate(1);にしても、引数を変えてみてもSQLSTATE[42000]: Syntax error or access violation: 1055 のエラーになってしまいました。 下記を参考にLaravelフォルダ/config/database.phpファイル内のmysqlの'strict'をtrue→falseに変更するとエラーは出なくなりましたが、やはりどこにつけても重複が解消されません。 https://zero-lara.com/2018/04/15/post-259/ 私のデータの取得方法が間違えてますでしょうか?? お手数ですが他にアドバイスありましたら教えていただけると幸いです。
guest

0

・Viewに渡された良いねしたカード$user->good_cards()の中身は毎回正しく①②③④全カードの並び替えになっています。ダブっていません。
($cards->inRandomOrder($pages)->paginate(4);に変えてみて確認)

上記の「$cards->inRandomOrder($pages)->paginate(4);」が何をしているかですが、
inRandomOrder($pages)は、最終的にorder by RAND($pages) になります。
また、paginate(4)は、データを4件取得します。

この時点で問題が無いのは、4件のデータを並び替えて4件取得しているためで、ダブりようがないからです。

「$cards->inRandomOrder($pages)->paginate(1);」の場合も同様に、
4件のデータをランダムに並び替えて1件を取得しています。

ページ遷移をするたびに、上記クエリー(ランダムで並び替えて1件取得)を発行しますので、
本来であれば、ダブりが出るのは当然なのですが、
RAND()に同一引数を指定した場合は、毎回並び替えの結果が同じになると言う仕様があります。

そのため、$pageが同一であれば、並び替えの結果は毎回同じになるはずですので、
ダブりが出ると言う事は、$pageがページごとに違っていると考えられます。

まずは、$pageをdumpして、全ページで同一の値かどうかを確認してみて下さい。

投稿2019/07/10 08:21

Calmar

総合スコア43

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Rint

2019/07/13 02:37

ご回答いただきありがとうございます。 確認したところ、$pagesは全ページで同じになっています。 不思議なのは、範囲を「自分が保存したカード」にすると問題は起きないのに、「良いねしたカード」にした時だけ問題が起こる点です。 1件ずつのページネーションだけでなく、paginate(3)など、カード全件を1ページに表示しない場合に、ダブりが発生してしまいます。
Calmar

2019/07/16 02:18

> 確認したところ、$pagesは全ページで同じになっています。 なるほど、そうしますと別の問題ですね。 > 範囲を「自分が保存したカード」にすると問題は起きないのに、「良いねしたカード」にした時だけ問題が起こる点です もしかすると、belongsToMany()の問題かも知れません。 一度、 $cards->inRandomOrder($pages)->paginate(4); の、$pagesを固定(2341等)して、毎回同一の並び順になっているか確認いただけますか?
Rint

2019/07/23 15:23

何度もご検討いただきありがとうございます。 $pagesを固定すると、毎回同一の並び順になっています。(毎回同じダブり方をします)
Calmar

2019/07/24 00:37

こちらこそ、ずばりの回答できずに申し訳ないです。 いくつか思った事がありましたので書き連ねてみます。 ・$user->good_cards()の件数って、4件でしょうか? (5件以上selectされているならダブると思われます。) ・クエリビルダに書き換えてみれば、変化があるかもしれません。 (joinを利用した素のクエリーが書けるので) ・user、card、goodのテーブル構造&データを教えていただければ、こちらで構築して、試してみる事も可能です。
guest

0

見当違いな回答のため、取り下げます。

投稿2019/07/02 10:04

編集2019/07/02 11:00
Calmar

総合スコア43

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問