MDL-85637 question: Optimize max question version SQL query

This commit is contained in:
Iñigo Zendegi
2026-03-11 11:41:03 +01:00
parent 65b51ef436
commit b726820c4f
8 changed files with 92 additions and 73 deletions
@@ -243,20 +243,11 @@ class custom_view extends \core_question\local\bank\view {
}
// Build the where clause.
$latestversion = 'qv.version = (SELECT MAX(v.version)
FROM {question_versions} v
JOIN {question_bank_entries} be
ON be.id = v.questionbankentryid
WHERE be.id = qbe.id AND v.status <> :substatus)';
// An additional condition is required in the subquery to account for scenarios
// where the latest version is hidden. This ensures we retrieve the previous
// "Ready" version instead of the hidden latest version.
$onlyready = '((qv.status = :status))';
$this->sqlparams = [
'status' => question_version_status::QUESTION_STATUS_READY,
'substatus' => question_version_status::QUESTION_STATUS_HIDDEN,
];
$this->sqlparams['status'] = question_version_status::QUESTION_STATUS_READY;
$conditions = [];
foreach ($this->searchconditions as $searchcondition) {
if ($searchcondition->where()) {
@@ -266,13 +257,20 @@ class custom_view extends \core_question\local\bank\view {
$this->sqlparams = array_merge($this->sqlparams, $searchcondition->params());
}
}
$majorconditions = ['q.parent = 0', $latestversion, $onlyready];
$latestversion = 'qv2.questionbankentryid IS NULL';
$majorconditions = ['q.parent = :parent', $latestversion, $onlyready];
$this->sqlparams = array_merge(['parent' => 0], $this->sqlparams);
// Get higher level filter condition.
$jointype = isset($this->pagevars['jointype']) ? (int)$this->pagevars['jointype'] : condition::JOINTYPE_DEFAULT;
$nonecondition = ($jointype === datafilter::JOINTYPE_NONE) ? ' NOT ' : '';
$separator = ($jointype === datafilter::JOINTYPE_ALL) ? ' AND ' : ' OR ';
// Build the SQL.
$sql = ' FROM {question} q ' . implode(' ', $joins);
$this->sqlparams['substatus'] = question_version_status::QUESTION_STATUS_HIDDEN;
$sql = ' FROM {question} q ' . implode(' ', $joins) .
' LEFT JOIN {question_versions} qv2 ON ( qv2.questionbankentryid = qv.questionbankentryid
AND qv2.version > qv.version
AND qv2.status <> :substatus
)';
$sql .= ' WHERE ' . implode(' AND ', $majorconditions);
if (!empty($conditions)) {
$sql .= ' AND ' . $nonecondition . ' ( ';
@@ -110,25 +110,33 @@ class custom_category_condition_helper extends \qbank_managecategories\helper {
'status' => question_version_status::QUESTION_STATUS_READY,
'substatus' => question_version_status::QUESTION_STATUS_HIDDEN,
];
$additionaljoins = "";
$additionalwhere = "";
if ($showallversions === 0) {
$additionaljoin = " LEFT JOIN {question_versions} qv2
ON ( qv2.questionbankentryid = qv.questionbankentryid
AND qv2.version > qv.version
AND qv2.status <> :substatus
)";
$additionalwhere = " AND qv2.questionbankentryid IS NULL";
}
$sql = "SELECT c.*,
(SELECT COUNT(1)
FROM {question} q
JOIN {question_versions} qv ON qv.questionid = q.id
JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
WHERE q.parent = '0'
$statuscondition
(SELECT COUNT(1)
FROM {question} q
JOIN {question_versions} qv ON qv.questionid = q.id
$additionaljoin
JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
WHERE q.parent = '0'
$statuscondition
AND c.id = qbe.questioncategoryid
AND ($showallversions = 1
OR (qv.version = (SELECT MAX(v.version)
FROM {question_versions} v
JOIN {question_bank_entries} be ON be.id = v.questionbankentryid
WHERE be.id = qbe.id AND v.status <> :substatus)
)
)
) AS questioncount
FROM {question_categories} c
WHERE c.contextid IN ($contexts) $topwhere
ORDER BY $sortorder";
$additionalwhere
) AS questioncount
FROM {question_categories} c
WHERE c.contextid IN ($contexts) $topwhere
ORDER BY $sortorder";
return $DB->get_records_sql($sql, $params);
}
+9 -8
View File
@@ -750,22 +750,23 @@ class view {
$extracondition = '';
if (!$showhiddenquestion) {
// If Show hidden question option is off, then we need get the latest version that is not hidden.
$extracondition = ' AND v.status <> :hiddenstatus';
$extracondition = ' AND qv2.status <> :hiddenstatus';
$this->sqlparams = array_merge($this->sqlparams, ['hiddenstatus' => question_version_status::QUESTION_STATUS_HIDDEN]);
}
$latestversion = "qv.version = (SELECT MAX(v.version)
FROM {question_versions} v
JOIN {question_bank_entries} be
ON be.id = v.questionbankentryid
WHERE be.id = qbe.id $extracondition)";
// Get higher level filter condition.
$jointype = isset($this->pagevars['jointype']) ? (int)$this->pagevars['jointype'] : condition::JOINTYPE_DEFAULT;
$nonecondition = ($jointype === datafilter::JOINTYPE_NONE) ? ' NOT ' : '';
$separator = ($jointype === datafilter::JOINTYPE_ALL) ? ' AND ' : ' OR ';
// Build the SQL.
$sql = ' FROM {question} q ' . implode(' ', $joins);
$sql .= ' WHERE q.parent = 0 AND ' . $latestversion;
$sql = ' FROM {question} q ' . implode(' ', $joins) .
' LEFT JOIN {question_versions} qv2 ON ( qv2.questionbankentryid = qv.questionbankentryid
AND qv2.version > qv.version' .
$extracondition .
')' .
' WHERE q.parent = :parent
AND qv2.questionbankentryid IS NULL';
$this->sqlparams = array_merge(['parent' => 0], $this->sqlparams);
if (!empty($conditions)) {
$sql .= ' AND ' . $nonecondition . ' ( ';
$sql .= implode($separator, $conditions);
@@ -94,21 +94,21 @@ class question_category_selector implements renderable, templatable {
$topwhere = $top ? '' : 'AND c.parent <> 0';
$statuscondition = "AND (qv.status = '" . question_version_status::QUESTION_STATUS_READY . "' " .
" OR qv.status = '" . question_version_status::QUESTION_STATUS_DRAFT . "' )";
$substatuscondition = "AND v.status <> '" . question_version_status::QUESTION_STATUS_HIDDEN . "' ";
$substatuscondition = "AND qv2.status <> '" . question_version_status::QUESTION_STATUS_HIDDEN . "' ";
$sql = "SELECT c.*,
(SELECT COUNT(1)
FROM {question} q
JOIN {question_versions} qv ON qv.questionid = q.id
LEFT JOIN {question_versions} qv2 ON ( qv2.questionbankentryid = qv.questionbankentryid
AND qv2.version > qv.version
$substatuscondition
)
JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
WHERE q.parent = '0'
$statuscondition
AND c.id = qbe.questioncategoryid
AND ({$showallversions} = 1
OR (qv.version = (SELECT MAX(v.version)
FROM {question_versions} v
JOIN {question_bank_entries} be ON be.id = v.questionbankentryid
WHERE be.id = qbe.id $substatuscondition)
)
OR qv2.questionbankentryid IS NULL
)
) AS questioncount
FROM {question_categories} c
+21 -15
View File
@@ -81,22 +81,28 @@ function get_questions_category(object $category, bool $noparent, bool $recurse
list($usql, $params) = $DB->get_in_or_equal($categorylist);
// Get the latest version of a question.
$version = '';
if ($latestversion) {
$version = 'AND (qv.version = (SELECT MAX(v.version)
FROM {question_versions} v
JOIN {question_bank_entries} be
ON be.id = v.questionbankentryid
WHERE be.id = qbe.id) OR qv.version is null)';
}
$questions = $DB->get_records_sql("SELECT q.*, qv.status, qc.id AS category
FROM {question} q
JOIN {question_versions} qv ON qv.questionid = q.id
JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
JOIN {question_categories} qc ON qc.id = qbe.questioncategoryid
WHERE qc.id {$usql} {$npsql} {$version}
ORDER BY qc.id, q.qtype, q.name", $params);
$sql = "SELECT q.*, qv.status, qc.id AS category
FROM {question} q
JOIN {question_versions} qv ON qv.questionid = q.id";
if ($latestversion) {
$sql .= " LEFT JOIN {question_versions} qv2 ON ( qv2.questionbankentryid = qv.questionbankentryid
AND qv2.version > qv.version
)";
}
$sql .= " JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
JOIN {question_categories} qc ON qc.id = qbe.questioncategoryid
WHERE qc.id {$usql} {$npsql}";
if ($latestversion) {
$sql .= " AND (qv2.questionbankentryid IS NULL
OR qv.version IS NULL)";
}
$sql .= " ORDER BY qc.id, q.qtype, q.name";
$questions = $DB->get_records_sql($sql, $params);
// Iterate through questions, getting stuff we need.
$qresults = [];
foreach ($questions as $question) {
+5 -6
View File
@@ -643,16 +643,15 @@ class question_finder implements data_source_interface {
$sql = "SELECT q.id, q.id AS id2
FROM {question} q
JOIN {question_versions} qv ON qv.questionid = q.id
LEFT JOIN {question_versions} qv2 ON ( qv2.questionbankentryid = qv.questionbankentryid
AND qv2.version > qv.version
AND qv2.status = :readystatusqv
)
JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
WHERE qbe.questioncategoryid {$qcsql}
AND q.parent = 0
AND qv.status = :readystatus
AND qv.version = (SELECT MAX(v.version)
FROM {question_versions} v
JOIN {question_bank_entries} be
ON be.id = v.questionbankentryid
WHERE be.id = qbe.id
AND v.status = :readystatusqv)
AND qv2.questionbankentryid IS NULL
{$extraconditions}";
return $DB->get_records_sql_menu($sql, $qcparams + $extraparams);
@@ -120,7 +120,7 @@ final class questionbank_test extends \advanced_testcase {
$q1v3 = $questiongenerator->update_question($q1v2, null, ['name' => 'Q1V3']);
// The second question has the first version in status draft, the second version in status ready,
// and third version in status draft.
$q2v1 = $questiongenerator->create_question('numerical', null, ['name' => 'Q2V2', 'category' => $cat->id,
$q2v1 = $questiongenerator->create_question('numerical', null, ['name' => 'Q2V1', 'category' => $cat->id,
'status' => question_version_status::QUESTION_STATUS_DRAFT, ]);
$q2v2 = $questiongenerator->update_question($q2v1, null, ['name' => 'Q2V2',
'status' => question_version_status::QUESTION_STATUS_READY, ]);
@@ -133,11 +133,19 @@ final class questionbank_test extends \advanced_testcase {
'status' => question_version_status::QUESTION_STATUS_DRAFT, ]);
$q3v3 = $questiongenerator->update_question($q3v2, null, ['name' => 'Q3V3',
'status' => question_version_status::QUESTION_STATUS_DRAFT]);
// The fourth question has the first and the second versions in status ready, and the third version in status draft.
$q4v1 = $questiongenerator->create_question('essay', null, ['name' => 'Q4V1', 'category' => $cat->id,
'status' => question_version_status::QUESTION_STATUS_READY, ]);
$q4v2 = $questiongenerator->update_question($q4v1, null, ['name' => 'Q4V2',
'status' => question_version_status::QUESTION_STATUS_READY, ]);
$q4v3 = $questiongenerator->update_question($q4v2, null, ['name' => 'Q4V3',
'status' => question_version_status::QUESTION_STATUS_DRAFT]);
// Test the returned array of questions in that category is the desired one with version three of the first
// question, version two of the second question, and the third question omitted completely since there are
// only draft versions.
$this->assertEquals([$q1v3->id => $q1v3->id, $q2v2->id => $q2v2->id],
// question, version two of the second question, the third question omitted completely since there are
// only draft versions, and the version two of the fourth question.
$this->assertEquals(
[$q1v3->id => $q1v3->id, $q2v2->id => $q2v2->id, $q4v2->id => $q4v2->id],
question_bank::get_finder()->get_questions_from_categories([$cat->id], ""));
}
}
+4 -5
View File
@@ -104,15 +104,14 @@ if ($requestedqtype) {
FROM (SELECT qv.id as versionid, qc.contextid, 1 AS numquestions
FROM {question} q
JOIN {question_versions} qv ON qv.questionid = q.id
LEFT JOIN {question_versions} qv2 ON ( qv2.questionbankentryid = qv.questionbankentryid
AND qv2.version > qv.version
)
JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
JOIN {question_categories} qc ON qc.id = qbe.questioncategoryid
JOIN {context} con ON con.id = qc.contextid
$sqlqtypetest
AND qv.version = (SELECT MAX(v.version)
FROM {question_versions} v
JOIN {question_bank_entries} be
ON be.id = v.questionbankentryid
WHERE be.id = qbe.id)
AND qv2.questionbankentryid IS NULL
AND (q.parent = 0 OR q.parent = q.id)) data
GROUP BY data.contextid, data.versionid) result
JOIN {context} con ON con.id = result.contextid