【问题标题】:Repeat query when conditions aren't met不满足条件时重复查询
【发布时间】:2021-10-27 04:25:09
【问题描述】:

我正在做一个小测验。测验有 15 个问题,对于测验我需要 quiztype '1' 的 5 个问题,quizType '2' 的 5 个问题和 quizType '3' 的 5 个问题。现在我正在通过循环计算 quiztype '1' 和 quiztype '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 来获得三个查询的组合结果,这些查询分别选择五个 特定 quizType 的随机问题。
  • union 没听说过,我会研究一下,但它仍然会按随机顺序获取所有条目吗?还是先 5x quiztype=1,然后 5x quiztype=2,最后一个条目的 5x quiztype=3?
  • stackoverflow.com/questions/4715820/… 应该回答这个问题。
  • 您使用的是什么版本的 MySQL? SELECT version();
  • 这是输出:10.4.20-MariaDB,我在 xampp 工作

标签: php mysql sql pdo prepared-statement


【解决方案1】:

感谢 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();

虽然仍有一些问题,但我会先尝试自己解决这个问题,如果最终需要,请提出另一个问题。

【讨论】:

  • 您的查询中没有不受信任的数据。无需使用准备好的语句来防御 SQl 注入,并且由于您只运行一次查询,因此也没有性能优势。只需使用$this->db->query(...)
  • 为什么$this->db->query() 更可取?它更快吗?
【解决方案2】:

你也可以这样组合。

UNION 很容易通过注意到 SELECT 语句中的区别只是选择 form 值 1、2 和 3 来避免。在 SQL 中,使用 form IN (1, 2, 3) 很容易做到这一点。

问题在于我们不能像您最初那样轻松地使用 LIMIT 5,因为所有 15 行现在都在相同的结果中。

这就是window functions 发挥作用的地方。我们现在可以使用窗口规范来处理这些行,以隔离和操作组(按分区)行。

下面的例子是ROW_NUMBER() OVER (PARTITION BY form ORDER BY rand()) AS seq

简而言之,这会派生一个新列(参见:derived column),其内容是该行在具有匹配 form 值的行组中的位置(行号)(在 @ 中表示987654330@ 条款)并按照OVER 子句的ORDER BY 条款指定的顺序。

您的要求因所需的随机顺序而略微复杂。看到这个窗口函数的使用如何提供这种漂亮的行号排序并不容易。您可以通过将 rand() 替换为更易于识别的 ORDER BY exercise 来测试这一点,这是我选择用来表示一些练习标识符的列。

WITH clauseCommon Table Expression - CTE 术语类似于 derived tableview,但提供了更多功能,如递归。我们可以像任何VIEWDerived Table、基表等一样访问它。

在下面的 CTE 术语中,我们选择与 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 |
+----------+------+-----+

【讨论】:

  • 太棒了!我不能赞成你的评论,因为我是 stackoverflow 的新手。虽然我会,如果可以的话:p
  • @SingingInTheRain 不客气。如果您想“接受”它,您可以通过投票控件附近的“复选标记”来实现。
  • 这个答案可能包括更慷慨的解释。
  • @SingingInTheRain 几分钟后,我将添加一些关于 CTE 术语和窗口函数的 cmets,这对您来说可能都是新的。
猜你喜欢
  • 1970-01-01
  • 2013-08-10
  • 2019-11-07
  • 1970-01-01
  • 2014-10-20
  • 1970-01-01
  • 2021-03-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多