不满足条件时重复查询

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 clauseCommon Table Expression - CTE 术语类似于 derived tableview,但提供更多功能,如递归。我们可以像访问任何VIEWDerived 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 |
+----------+------+-----+