我正在使用 PHP 和 MySQL 构建一个在线考试平台。我对数据库有以下要求:
有没有办法让这 30 名学生按不同的顺序收到 20 个问题?例如,学生 A 的问题 1 将是学生 B 的问题 16?
TL; DR - 最终想出了如何在一次通过中做到这一点 - 答案在最后。我保留了原始答案,以便这里的人们可以从我自己的(吱吱作响的 ;-) )思维过程中学习!
更新:下面提供了一个替代答案。
这被证明是相当棘手的。为了解决这个问题,我做了以下(见小提琴here)
我创建了表:
CREATE TABLE test_question
(
tq_id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
q_text VARCHAR (256) NOT NULL
);
Run Code Online (Sandbox Code Playgroud)
就本演示而言,问题的文本并不重要:
INSERT INTO test_question (q_text) VALUES ('q1');
INSERT INTO test_question (q_text) VALUES ('q2');
INSERT INTO test_question (q_text) VALUES ('q3');
INSERT INTO test_question (q_text) VALUES ('q4');
INSERT INTO test_question (q_text) VALUES ('q5');
INSERT INTO test_question (q_text) VALUES ('q6');
Run Code Online (Sandbox Code Playgroud)
然后分配的问题部分:
CREATE TABLE assigned_question
(
aq_id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
st_id INTEGER, -- Student id
qu_id INTEGER, -- Question id
q_text VARCHAR (256) -- text of the question
);
Run Code Online (Sandbox Code Playgroud)
然后,为了生成一个随机的 student_question 组合列表,即填充assigned_question 表,我使用了两个RECURSIVE WITHs(或CTEs):
INSERT INTO assigned_question (st_id, qu_id)
(
WITH RECURSIVE student AS (
SELECT 1 AS s_no
UNION ALL
SELECT s_no + 1 AS value
FROM student
WHERE student.s_no <= 5 -- 6 students
),
question AS (
SELECT 1 AS q_no
UNION ALL
SELECT q_no + 1 AS value
FROM question
WHERE question.q_no <= 4 -- 5 questions
)
SELECT * FROM student
CROSS JOIN question
ORDER BY s_no, RAND()
);
Run Code Online (Sandbox Code Playgroud)
这里重要的一点是RAND()对结果集的问题部分进行随机排序的函数。您为学生分配一个随机的学生编号,然后告诉他们根据他们的 ID 编号从主表中进行选择。
现在,我不能保证每个学生都会有一套完全不同的问题,但他们中的大多数人应该有不同的问题清单——如果你不允许他们互相交谈并告诉他们回答所有问题问题,按顺序,即使答案是空白,你也有一种合理的方法来检查他们是否在回答他们自己的问题。少数有匹配问题列表的学生无论如何都无法交流!
然后,将这个assigned_question 表连接到test_question 表,这样:
SELECT aq.aq_id, aq.st_id, aq.qu_id, tq.q_text
FROM assigned_question aq
JOIN test_question tq
ON aq.qu_id = tq.tq_id;
Run Code Online (Sandbox Code Playgroud)
结果(示例,请参阅小提琴以获取完整结果):
aq_id st_id qu_id q_text
1 1 2 q2
2 1 5 q5
... results omitted for brevity
6 2 4 q4
7 2 3 q3
Run Code Online (Sandbox Code Playgroud)
因此,我们可以看到学生 1 必须先回答问题 2 和 5,而学生 2 将首先回答问题 4 和 3(完整结果如下)。
这可能是您在纯 MySQL 中所能做的最好的事情。
我已经“摆弄”了更多(请原谅双关语!:-))实际上可以一次完成,如下所示:
(旁白:另请参阅这个更优雅的小提琴,它更类似于我的第二个答案)。
INSERT INTO assigned_question (st_id, qu_id, q_text)
SELECT s_no, q_no, q_text FROM
(
SELECT * FROM
(
WITH RECURSIVE student AS (
SELECT 1 AS s_no
UNION ALL
SELECT s_no + 1 AS value
FROM student
WHERE student.s_no <= 5 -- students - 6 questions
),
question AS (
SELECT 1 AS q_no
UNION ALL
SELECT q_no + 1 AS value
FROM question
WHERE question.q_no <= 4 -- students - 5 questions
)
SELECT * FROM student
CROSS JOIN question
ORDER BY s_no, RAND()
) AS z
JOIN test_question tq ON z.q_no = tq.tq_id) AS a
ORDER BY a.s_no, RAND();
Run Code Online (Sandbox Code Playgroud)
结果:
aq_id st_id qu_id q_text
1 1 3 q3
2 1 2 q2
... results again omitted for brevity...
6 2 4 q4
7 2 3 q3
Run Code Online (Sandbox Code Playgroud)
因此,学生 1 将首先回答问题 3 和 2,学生 2 将首先回答问题 4 和 3。在检查时,我用最后一个小提琴生成的结果表明,6 名学生中的每个人的前 2 个问题都不同,因此它似乎运行良好。但是请记住,该RAND()功能并不能保证这一点,并且可能会有学生的问题顺序相同 - 但对于 30 名学生和 20 个问题,这是极不可能的!哼!ps 欢迎来到论坛!:-)
你可以做的是“错开”(见下面的结果解释)学生提出的问题。所有必要的 DDL 表构造和 DML 数据都在帖子的底部 - 我将只介绍下面 SQL 的“核心”。小提琴可用在这里。
SELECT
ROW_NUMBER() OVER () AS "Num", -- all records - not strictly necessary
t.s_id,
CONCAT
(
t.q_id,
CASE
WHEN t.q_id % 100 BETWEEN 11 and 13 THEN "th"
WHEN t.q_id % 10 = 1 THEN "st"
WHEN t.q_id % 10 = 2 THEN "nd"
WHEN t.q_id % 10 = 3 THEN "rd"
ELSE "th"
END
) AS "Q_ID_2",
((s_id + q_id - 2 ) % (SELECT COUNT(*) FROM question )) + 1 AS "Question_id",
q.question_text
FROM test t
JOIN question q ON ((s_id + q_id - 2 ) % (SELECT COUNT(*) FROM question )) + 1 = q.question_id
ORDER BY s_id, q_id;
Run Code Online (Sandbox Code Playgroud)
这里的关键位是模数 (%) 运算符并添加 s_id 和 q_id。CONCAT获得序数的整个过程只是在炫耀!:-) 在这里找到序数片段。
结果:
Num s_id Q_ID_2 Question_id question_text
1 1 1st 1 question_1_txt
2 1 2nd 2 question_2_txt
Run Code Online (Sandbox Code Playgroud)
所以,如我们所见,第一个学生的第一个问题是 question_id 1,即 question_1_text。“那又怎样? ”,你可能会说,这不是最初的问题!
但是,当我们查看第二个学生的问题时,我们会得到:
Num s_id Q_ID_2 Question_id question_text
9 2 1st 2 question_2_txt
10 2 2nd 3 question_3_txt
Run Code Online (Sandbox Code Playgroud)
现在,第二个学生的第一个问题是 question_id 2,即问题 2 与他邻居的第一个问题不同 - 这是 OP 要求的!
很酷的是,如果我们看一下学生 4 和 5 之间的“接口”,我们会得到:
Num s_id Q_ID_2 Question_id question_text
31 4 7th 2 question_2_txt
32 4 8th 3 question_3_txt
33 5 1st 5 question_5_txt
34 5 2nd 6 question_6_txt
Run Code Online (Sandbox Code Playgroud)
学生 4 回答的最后一个问题是 2 和 3 - 即问题已经“环绕” - 所以没有学生错过任何问题。
这实际上只是一种智力练习。对于(接近)真正的随机性,我的第一个答案更好——我能想到的唯一优势是它可以更容易地分发试卷——所有监考人员(监督考试的人)要做的就是将一张纸移到回到每张新办公桌前。希望你喜欢!:-)
============================== DDL 和 DML ================
CREATE TABLE question
(
question_id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
question_text VARCHAR (30) NOT NULL
);
INSERT INTO question (question_text) VALUES ('question_1_txt');
INSERT INTO question (question_text) VALUES ('question_2_txt');
INSERT INTO question (question_text) VALUES ('question_3_txt');
INSERT INTO question (question_text) VALUES ('question_4_txt');
INSERT INTO question (question_text) VALUES ('question_5_txt');
INSERT INTO question (question_text) VALUES ('question_6_txt');
INSERT INTO question (question_text) VALUES ('question_7_txt');
INSERT INTO question (question_text) VALUES ('question_8_txt');
CREATE TABLE student
(
student_id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
student_name VARCHAR (50) NOT NULL,
UNIQUE (student_name)
);
INSERT INTO student (student_name) VALUES ('student_1');
INSERT INTO student (student_name) VALUES ('student_2');
INSERT INTO student (student_name) VALUES ('student_3');
INSERT INTO student (student_name) VALUES ('student_4');
INSERT INTO student (student_name) VALUES ('student_5');
INSERT INTO student (student_name) VALUES ('student_6');
CREATE TABLE test
(
test_id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
s_id INTEGER NOT NULL,
q_id INTEGER NOT NULL
);
INSERT INTO test (s_id, q_id)
SELECT * FROM (
SELECT s.student_id, tab1.question_id
FROM student s
CROSS JOIN (SELECT q2.question_id FROM question q2)
AS tab1) AS tab2
ORDER BY student_id, question_id;
Run Code Online (Sandbox Code Playgroud)