私の問題は、ユーザーに同じ質問を 2 回以上行いたくないのですが、取得した質問の記録をどのように保持できますか? すべてのユーザーごとにテーブルを作成する必要がありますか? ロード時間が長くなりませんか?
はい、しかしおそらくそれほどではありません。
1 つの余分なテーブルを保持userId, questionId
し、さまざまなユーザーに既に尋ねられた質問をそこに挿入します。
質問 123 をユーザー 456 に尋ねると、単一の質問を実行します。INSERT
INSERT INTO askedQuestions (userId, questionId) VALUES (456, 123);
次に、から質問を抽出questions
しますLEFT JOIN
SELECT questions.* FROM questions
LEFT JOIN askedQuestions ON (questions.id = askedQuestions.questionId AND askedQuestions.userId = {$_SESSION['userId']} )
WHERE askedQuestions.userId IS NULL
ORDER BY RAND() LIMIT 1;
askedQuestions
に索引付けしておくと(userId, questionId)
、結合が非常に効率的になります。
RAND()に関する注意事項
このようなテーブルでの選択は、 で行うべきではありません。これは、テーブル内のすべての行ORDER BY RAND()
を取得してから、それらの 1 つを出力します。通常、ランダムに を選択し、それで質問を選択します。そのほうがはるかに高速です。しかし、ここでは、質問がまだそのユーザーに尋ねられていないという保証はなく、より高速なクエリは失敗する可能性があります。questionId
questionId
ほとんどの質問がまだ自由にできる場合は、
WHERE questions.questionId IN ( RAND(N), RAND(N), RAND(N), ... )
AND askedQuestions.userId IS NULL LIMIT 1
ここで、N は質問の数です。抽出した乱数の少なくとも 1 つがまだ空いている可能性があります。はIN
パフォーマンスが低下するため、 の数とのバランスを取る必要がありますRAND
。ほとんどすべての質問が行われると、一致する可能性が低くなり、多くの s を使用してもクエリが何も返さない可能性がありますRAND
(また、 s が重複した ID を生成し始めるため、これは誕生日のパラドックスRAND
として知られています)。
両方の長所を活かす方法の 1 つは、最大試行回数を 3 回に固定することです (または、残っている質問の数に基づいて、さらに良い方法です)。
X 回、(PHP で) 1 から 1000 までの Y 個のランダム ID のセットを生成し、から取得しようとし(userId, questionId)
ますaskedQuestions
。テーブルはシンでインデックス付けされているため、これは非常に高速です。失敗した場合、抽出されたquestionId
ものはランダムで無料であり、実行できます
SELECT * FROM questions WHERE id = {$tuple['questionId']};
これも非常に高速です。X 回、つまり X 回成功した場合、すべての Y 個のランダムな questionId が既に質問されているものとして登録され、完全なクエリを実行します。ほとんどのユーザーはほぼ瞬時に処理され (2 つの非常に高速なクエリ)、少数の本当に熱心なユーザーだけがより多くの処理を必要とします。ユーザーが質問を使い果たしたことを警告するために、ある種の警告を設定することができます。