不满足条件时重复查询
Repeat query when conditions aren't met
我正在做一个小测验。测验有 15 个问题,对于测验,我需要 5 个测验类型为“1”的问题、5 个测验类型为“2”的问题和 5 个测验类型为“3”的问题。现在我正在通过循环计算测验类型“1”和测验类型“2”,如果不满足循环外的条件,我会得到 15 个新条目并重复循环。我想知道,有没有更好的方法在我的查询中执行此操作而不是使用 2 个对象?
这是我的代码:
public function checkVariety($quizType, $data)
{
$i=0;
$i2=0;
foreach($quizType as $type) {
if ($type=='1') {
$i++;
}
if ($type=='2') {
$i2++;
}
}
if($i=='5' AND $i2=='5') {
$this->startQuiz($data);
return true;
} else {
$this->getRandom();
return false;
}
}
public function getRandom()
{
$stmt = $this->db->prepare("
SELECT id, quiz_type
FROM quiz
ORDER BY rand()
LIMIT 15
");
$stmt->execute();
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$quizType[] = $row['quiz_type'];
$data[] = $row['id'];
}
$this->checkVariety($quizType, $data);
return true;
}
多亏了 UNION 方法,我让它部分工作了。
$stmt = $this->db->prepare("
SELECT *
FROM (SELECT * FROM exercises as e1 WHERE e1.form='1' ORDER BY rand() LIMIT 5) as f
UNION
SELECT *
FROM (SELECT * FROM exercises as e1 WHERE e1.form='2' ORDER BY rand() LIMIT 5) as f2
UNION
SELECT *
FROM (SELECT * FROM exercises as e1 WHERE e1.form='3' ORDER BY rand() LIMIT 5) as f3
ORDER BY rand()
");
$stmt->execute();
虽然还有一些问题,但我会先尝试自己解决,如果我最终需要,再开一个问题。
你也可以这样组合。
通过注意 SELECT
语句中的差异只是选择 form
值 1、2 和 3,很容易避免 UNION
。在 SQL 中,这很容易用 form IN (1, 2, 3)
.
完成
问题在于我们无法像您最初那样轻松地使用 LIMIT 5
,因为所有 15 行现在都在相同的结果中。
这就是 window functions
发挥作用的地方。我们现在可以使用 window 规范来处理这些行,以隔离和操作行组(按分区)。
下面的例子是ROW_NUMBER() OVER (PARTITION BY form ORDER BY rand()) AS seq
.
简而言之,这派生了一个新列(参见:derived column
),其内容是该行在具有匹配 form
的行组中的位置(行号)值(在 PARTITION BY
项中表示)并按 OVER
子句的 ORDER BY
项指定的顺序。
您的要求因所需的随机顺序而略微复杂。很难看出这个 window 函数是如何提供这种漂亮的行号排序的。您可以通过将 rand()
术语替换为更易识别的术语 ORDER BY exercise
来对此进行测试,这是我选择代表某些练习标识符的列。
WITH clause
或 Common Table Expression - CTE
术语类似于 derived table
或 view
,但提供更多功能,如递归。我们可以像访问任何VIEW
、Derived Table
、基础table等
一样访问它
在接下来的 CTE 术语中,我们 select 匹配 3 种形式的所有行,并分配/生成一个包含行号的新 seq
列(每个分区内从 1 到 n) ,以便稍后我们可以只使用 seq <= 5
将结果限制为每个分区的前 5 行 (form
)。
WITH cte AS (
SELECT *
, ROW_NUMBER() OVER (PARTITION BY form ORDER BY rand()) AS seq
FROM exercises
WHERE form IN (1, 2, 3)
)
SELECT * FROM cte
WHERE seq <= 5
ORDER BY form, seq
;
测试数据结果:
+----------+------+-----+
| exercise | form | seq |
+----------+------+-----+
| 15 | 1 | 1 |
| 8 | 1 | 2 |
| 10 | 1 | 3 |
| 16 | 1 | 4 |
| 6 | 1 | 5 |
| 29 | 2 | 1 |
| 24 | 2 | 2 |
| 26 | 2 | 3 |
| 20 | 2 | 4 |
| 25 | 2 | 5 |
| 41 | 3 | 1 |
| 46 | 3 | 2 |
| 47 | 3 | 3 |
| 40 | 3 | 4 |
| 51 | 3 | 5 |
+----------+------+-----+
我正在做一个小测验。测验有 15 个问题,对于测验,我需要 5 个测验类型为“1”的问题、5 个测验类型为“2”的问题和 5 个测验类型为“3”的问题。现在我正在通过循环计算测验类型“1”和测验类型“2”,如果不满足循环外的条件,我会得到 15 个新条目并重复循环。我想知道,有没有更好的方法在我的查询中执行此操作而不是使用 2 个对象? 这是我的代码:
public function checkVariety($quizType, $data)
{
$i=0;
$i2=0;
foreach($quizType as $type) {
if ($type=='1') {
$i++;
}
if ($type=='2') {
$i2++;
}
}
if($i=='5' AND $i2=='5') {
$this->startQuiz($data);
return true;
} else {
$this->getRandom();
return false;
}
}
public function getRandom()
{
$stmt = $this->db->prepare("
SELECT id, quiz_type
FROM quiz
ORDER BY rand()
LIMIT 15
");
$stmt->execute();
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$quizType[] = $row['quiz_type'];
$data[] = $row['id'];
}
$this->checkVariety($quizType, $data);
return true;
}
多亏了 UNION 方法,我让它部分工作了。
$stmt = $this->db->prepare("
SELECT *
FROM (SELECT * FROM exercises as e1 WHERE e1.form='1' ORDER BY rand() LIMIT 5) as f
UNION
SELECT *
FROM (SELECT * FROM exercises as e1 WHERE e1.form='2' ORDER BY rand() LIMIT 5) as f2
UNION
SELECT *
FROM (SELECT * FROM exercises as e1 WHERE e1.form='3' ORDER BY rand() LIMIT 5) as f3
ORDER BY rand()
");
$stmt->execute();
虽然还有一些问题,但我会先尝试自己解决,如果我最终需要,再开一个问题。
你也可以这样组合。
通过注意 SELECT
语句中的差异只是选择 form
值 1、2 和 3,很容易避免 UNION
。在 SQL 中,这很容易用 form IN (1, 2, 3)
.
问题在于我们无法像您最初那样轻松地使用 LIMIT 5
,因为所有 15 行现在都在相同的结果中。
这就是 window functions
发挥作用的地方。我们现在可以使用 window 规范来处理这些行,以隔离和操作行组(按分区)。
下面的例子是ROW_NUMBER() OVER (PARTITION BY form ORDER BY rand()) AS seq
.
简而言之,这派生了一个新列(参见:derived column
),其内容是该行在具有匹配 form
的行组中的位置(行号)值(在 PARTITION BY
项中表示)并按 OVER
子句的 ORDER BY
项指定的顺序。
您的要求因所需的随机顺序而略微复杂。很难看出这个 window 函数是如何提供这种漂亮的行号排序的。您可以通过将 rand()
术语替换为更易识别的术语 ORDER BY exercise
来对此进行测试,这是我选择代表某些练习标识符的列。
WITH clause
或 Common Table Expression - CTE
术语类似于 derived table
或 view
,但提供更多功能,如递归。我们可以像访问任何VIEW
、Derived Table
、基础table等
在接下来的 CTE 术语中,我们 select 匹配 3 种形式的所有行,并分配/生成一个包含行号的新 seq
列(每个分区内从 1 到 n) ,以便稍后我们可以只使用 seq <= 5
将结果限制为每个分区的前 5 行 (form
)。
WITH cte AS (
SELECT *
, ROW_NUMBER() OVER (PARTITION BY form ORDER BY rand()) AS seq
FROM exercises
WHERE form IN (1, 2, 3)
)
SELECT * FROM cte
WHERE seq <= 5
ORDER BY form, seq
;
测试数据结果:
+----------+------+-----+
| exercise | form | seq |
+----------+------+-----+
| 15 | 1 | 1 |
| 8 | 1 | 2 |
| 10 | 1 | 3 |
| 16 | 1 | 4 |
| 6 | 1 | 5 |
| 29 | 2 | 1 |
| 24 | 2 | 2 |
| 26 | 2 | 3 |
| 20 | 2 | 4 |
| 25 | 2 | 5 |
| 41 | 3 | 1 |
| 46 | 3 | 2 |
| 47 | 3 | 3 |
| 40 | 3 | 4 |
| 51 | 3 | 5 |
+----------+------+-----+