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

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

ただいまの
回答率

87.96%

まだ1度も解いてない問題が表示されず、これまで表示されていた4択の選択肢も表示されない

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 1,516

score 15

前提・実現したいこと

お世話になります。
Laravelでクイズアプリを作ってみています。

「1度正解した問題を表示しない」という機能を実装したいです。

テーブルにはそれぞれ以下のようなデータが入っています。
・Questionsテーブル
イメージ説明
idが問題番号、topic_idがカテゴリ、question_textが問題文です。
イメージ説明
・Questions_optionsテーブル
イメージ説明
question_idがQuestionsテーブルのidと関係しています。
correctがその選択肢が正解かどうかを表しています。
イメージ説明
・test_answersテーブル
イメージ説明
user_idが回答者のid、question_idがQuestionsテーブルのidと関係、correctが正解したかどうかを表しています。
イメージ説明

入門書やネットで色々調べて試してみたのですがうまく行かず、初めて質問させていただきます。
SQLやPHPも学び始めたばかりですが、頑張りますのでご教授よろしくお願いします。
どのような関数を使えばいいのかやどういったサイトが参考になるのか等でも良いのでよろしくお願いいたします。

発生している問題・エラーメッセージ

1度解いて不正解になった(correctが0)問題のみ表示される...のはいいのですが、まだ1度も解いてない問題が、test_answersに登録されていないため表示されない。
また、実行してみるとこれまで表示されていた4択の選択肢が表示されなくなってしまいました。

今回の場合、topic_idが1、user_idが3で実行すると、test_answersテーブルのcorrectが0であるquestion_idが5と9の問題(中国の首都は?とスクリーンショットには載っていませんが9のイタリアの首都は?)のみ表示されます。

該当のソースコード

同じカテゴリ(topic_id)の問題からランダムで表示

    /**
     *
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function create($id)
    {
      // 元々書いてあったコード
      // $questions = Question::where('topic_id', $id)->inRandomOrder()->limit(10)->get();

      // 今回書いているコード
      $a_id = Auth::id();
      $questions = DB::table('questions')
                        ->where('topic_id', $id)
                        ->leftJoin('test_answers', 'questions.id', '=', 'test_answers.question_id')
                        ->where(function ($query) use($a_id){
                          $query->where('test_answers.user_id', $a_id)
                                ->where('test_answers.correct', '<>', 1);
                        })
                        ->inRandomOrder()->limit(10)->get();
      // 今回書いているコードここまで
      foreach ($questions as &$question) {
        $question->options = QuestionsOption::where('question_id', $question->id)->inRandomOrder()->get();
      }
      return view('tests.create', compact('questions'));
    }

補足情報(FW/ツールのバージョンなど)

・LaravelDailyさんのLaraQuizを元に作成しています。
https://github.com/LaravelDaily/Laraquiz-QuickAdminPanel

・環境構築にはMAMPを使っています。

・Question.php

<?php
namespace App;

use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Model;

/**
 * Class Question
 *
 * @package App
 * @property string $topic
 * @property text $question_text
 * @property text $code_snippet
 * @property text $answer_explanation
 * @property string $more_info_link
*/
class Question extends Model
{
    use SoftDeletes;

    protected $fillable = ['question_text', 'code_snippet', 'answer_explanation', 'more_info_link', 'topic_id'];

    public static function boot()
    {
        parent::boot();

        Question::observe(new \App\Observers\UserActionsObserver);
    }

    /**
     * Set to null if empty
     * @param $input
     */
    public function setTopicIdAttribute($input)
    {
        $this->attributes['topic_id'] = $input ? $input : null;
    }

    public function topic()
    {
        return $this->belongsTo(Topic::class, 'topic_id')->withTrashed();
    }

    public function options()
    {
        return $this->hasMany(QuestionsOption::class, 'question_id')->withTrashed();
    }
}


・QuestionsOption.php

<?php
namespace App;

use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Model;

/**
 * Class QuestionsOption
 *
 * @package App
 * @property string $question
 * @property string $option
 * @property tinyInteger $correct
*/
class QuestionsOption extends Model
{
    use SoftDeletes;

    protected $fillable = ['option', 'correct', 'question_id'];

    public static function boot()
    {
        parent::boot();

        QuestionsOption::observe(new \App\Observers\UserActionsObserver);
    }

    /**
     * Set to null if empty
     * @param $input
     */
    public function setQuestionIdAttribute($input)
    {
        $this->attributes['question_id'] = $input ? $input : null;
    }

    public function question()
    {
        return $this->belongsTo(Question::class, 'question_id')->withTrashed();
    }

}

・TestController.php

<?php

namespace App\Http\Controllers;

use DB;
use Auth;
use App\Test;
use App\TestAnswer;
use App\Topic;
use App\Question;
use App\QuestionsOption;
use Illuminate\Http\Request;
use App\Http\Requests\StoreTestRequest;

class TestsController extends Controller
{
    public function index()
    {
        $topics = Topic::all();

        return view('tests.index', compact('topics'));
    }

    /**
     *
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function create($id)
    {
      // 元々書いてあったコード
      // $questions = Question::where('topic_id', $id)->inRandomOrder()->limit(10)->get();

      // 今回書いているコード
      $a_id = Auth::id();
      $questions = DB::table('questions')
                        ->where('topic_id', $id)
                        ->leftJoin('test_answers', 'questions.id', '=', 'test_answers.question_id')
                        ->where(function ($query) use($a_id){
                          $query->Where('test_answers.user_id', $a_id)
                                ->Where('test_answers.correct', '<>', 1);
                        })
                        ->orWhere(function ($query) use($a_id){
                          $query->Where('test_answers.user_id', '<>',$a_id)
                                ->WhereNull('test_answers.question_id');
                        })
                        ->inRandomOrder()->limit(10)->get();
      // 今回書いているコードここまで
      foreach ($questions as &$question) {
        $question->options = QuestionsOption::where('question_id', $question->id)->inRandomOrder()->get();
      }
      return view('tests.create', compact('questions'));
    }

    /**
     * Store a newly solved Test in storage with results.
     *
     * @param  \App\Http\Requests\StoreResultsRequest  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        $result = 0;

        $test = Test::create([
            'user_id' => Auth::id(),
            'result'  => $result,
        ]);

        foreach ($request->input('questions', []) as $key => $question) {
            $status = 0;

            if ($request->input('answers.'.$question) != null
                && QuestionsOption::find($request->input('answers.'.$question))->correct
            ) {
                $status = 1;
                $result++;
            }
            TestAnswer::create([
                'user_id'     => Auth::id(),
                'test_id'     => $test->id,
                'question_id' => $question,
                'option_id'   => $request->input('answers.'.$question),
                'correct'     => $status,
            ]);
        }

        $test->update(['result' => $result]);

        return redirect()->route('results.show', [$test->id]);
    }

    /**
     * Delete all selected Test at once.
     *
     * @param Request $request
     */
    public function massDestroy(Request $request)
    {
        if ($request->input('ids')) {
            $entries = Topic::whereIn('id', $request->input('ids'))->get();

            foreach ($entries as $entry) {
                $entry->delete();
            }
        }
    }
}

・tests>create.blade.php一部抜粋

@section('content')
    <h3 class="page-title">@lang('quickadmin.laravel-quiz')</h3>
    {!! Form::open(['method' => 'POST', 'route' => ['tests.store']]) !!}

    <div class="panel panel-default">
        <div class="panel-heading">
            @lang('quickadmin.quiz')
        </div>
        <?php //dd($questions) ?>
    @if(count($questions) > 0)
        <div class="panel-body slider">
        <?php $i = 1; ?>
        @foreach($questions as $question)
            <div class="row">
                <div class="col-xs-12 form-group">
                    <div class="form-group" style="padding-left:100px">
                        <strong>Question {{ $i }}.<br />{!! nl2br($question->question_text) !!}</strong>

                        <input
                            type="hidden"
                            name="questions[{{ $i }}]"
                            value="{{ $question->id }}">
                    @foreach($question->options as $option)
                        <br>
                        <label class="radio-inline">
                            <input
                                type="radio"
                                name="answers[{{ $question->id }}]"
                                value="{{ $option->id }}">
                            {{ $option->option }}
                        </label>
                    @endforeach
                    </div>
                </div>
            </div>
        <?php $i++; ?>
        @endforeach
      </div>
    @endif

    </div>

    {!! Form::submit(trans('quickadmin.submit_quiz'), ['class' => 'btn btn-danger']) !!}
    {!! Form::close() !!}
@stop
  • 気になる質問をクリップする

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • m.ts10806

    2019/02/12 11:51

    仕様確認ですが、「正解するまで何回でも答えられる」「それまでは不正解のレコードがtest_answersテーブルに蓄積され続ける」ということですよね?

    キャンセル

  • m.ts10806

    2019/02/12 11:53

    ちなみにview側でdd($questions)されてますが、想定通りの取得したい情報は取れてましたか?

    キャンセル

  • t.mat

    2019/02/12 12:09

    >仕様確認ですが、「正解するまで何回でも答えられる」「それまでは不正解のレコードがtest_answersテーブルに蓄積され続ける」ということですよね?
    はい。そのようにしようと思っています。

    >ちなみにview側でdd($questions)されてますが、想定通りの取得したい情報は取れてましたか?
    問題は取れていたのですが、選択肢が取れていませんでした...。

    キャンセル

回答 2

+3

SQL イメージだけですが、全体的にこんな感じなるように組めばいけるかと

正解が存在しない (NOT EXISTS)

SELECT * FROM questions q
 WHERE NOT EXISTS ( SELECT 1 FROM test_answers a WHERE q.id = a.question_id AND a.correct = '1' AND a.user_id = ? )

正解が存在しない (NOT IN)

SELECT * FROM questions q
 WHERE q.id  NOT IN ( SELECT a.question_id FROM test_answers a WHERE a.correct = '1' AND a.user_id = ? )

正解が存在しない (LEFT JOIN)

SELECT * FROM questions q
 LEFT JOIN test_answers a 
   ON q.id = a.question_id AND a.user_id = ?
-- NULL か 1以外
 WHERE a.correct IS NULL OR NOT a.correct = '1'

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/02/12 12:36

    ありがとうございます!早速試してみます。

    キャンセル

checkベストアンサー

+2

問題id、ユーザーid、correct、answer_at
を保持するテーブルがあればよいのでは?
answer_atは未回答のときにはnullもしくは果てしない未来日として
回答した時点で日時を入れれば、answer_atが過去日で
correctが0のモノが間違えた質問になります

訂正

なんか効率悪そうなので考え方を換えます

create table question(qid int primary key,qtext varchar(100)
,a1 varchar(100)
,a2 varchar(100)
,a3 varchar(100)
,a4 varchar(100)
,correct_a int);
insert into question values
( 1,"q01_text","q01_a1","q01_a2","q01_a3","q01_a4",1),
( 2,"q02_text","q0_a1","q02_a2","q02_a3","q02_a4",2),
( 3,"q03_text","q03_a1","q03_a2","q03_a3","q03_a4",3),
( 4,"q04_text","q04_a1","q04_a2","q4_a3","q4_a4",3),
( 5,"q05_text","q05_a1","q05_a2","q5_a3","q5_a4",4),
( 6,"q06_text","q06_a1","q06_a2","q6_a3","q6_a4",2),
( 7,"q07_text","q07_a1","q07_a2","q7_a3","q7_a4",1),
( 8,"q08_text","q08_a1","q08_a2","q8_a3","q8_a4",3),
( 9,"q09_text","q09_a1","q09_a2","q9_a3","q9_a4",4),
(10,"q10_text","q10_a1","q10_a2","q0_a3","q0_a4",4),
(11,"q11_text","q11_a1","q11_a2","q1_a3","q1_a4",1);

create table user(uid int primary key,uname varchar(20));
insert into user values(1,'u1'),(2,'u2'),(3,'u3');

create table user_question(uqid int primary key auto_increment,uid int not null,qid int not null,correct tinyint default 0,unique(uid,qid));
insert into user_question(uid,qid,correct) values
(1,1,1),
(1,2,1),
(1,3,1),
(1,4,0),
(1,5,0),
(1,6,0),
(2,1,1),
(2,2,0),
(2,3,1),
(2,4,0),
(2,5,1),
(2,6,0);


だとします。
上記条件からuid=1の場合qid=1,2,3を除くもの、uid=2の場合qid=1,3,5を除くもの
uid=3の場合すべてが対象になります。

抽出

select * from question as t1
where not exists(select 1 from user_question where qid=t1.qid and uid=1 and correct=1)


uid=xxのxxを指定すれば間違っていたものおよび未回答の物が表示されます。

1問だけ表示したいならorder by とlimitを併用します

select * from question as t1
where not exists(select 1 from user_question where qid=t1.qid and uid=1 and correct=1)
order by rand()
limit 1

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/02/12 12:14

    回答ありがとうございます!参考になります!
    新たにテーブルを作成して、あらかじめnullを入れておいて、問題を解いたら更新するという事でしょうか??
    その場合、新たに問題を追加した場合は、追加するときにユーザ×問題数のレコードを作成して、nullを入れるという事でしょうか。

    キャンセル

  • 2019/02/12 12:51 編集

    効率が悪そうなので修正しました。追記分で確認してください

    キャンセル

  • 2019/02/13 12:46

    ありがとうございました!いただいた回答を元にやってみようと思います!

    キャンセル

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

  • ただいまの回答率 87.96%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • トップ
  • PHPに関する質問
  • まだ1度も解いてない問題が表示されず、これまで表示されていた4択の選択肢も表示されない