protected function get_iterator_sql(string $tablename, int $contextlevel, ?string $action = null, ?string $tablealias = null) {

        if (!$tablealias) {
            $tablealias = 'analysable';
        }

        $params = ['contextlevel' => $contextlevel, 'modelid' => $this->get_modelid()];
        $select = $tablealias . '.*, ' . \context_helper::get_preload_record_columns_sql('ctx');

        // We add the action filter on ON instead of on WHERE because otherwise records are not returned if there are existing
        // records for another action or model.
        $usedanalysablesjoin = ' LEFT JOIN {analytics_used_analysables} aua ON ' . $tablealias . '.id = aua.analysableid AND ' .
            '(aua.modelid = :modelid OR aua.modelid IS NULL)';

        if ($action) {
            $usedanalysablesjoin .= " AND aua.action = :action";
            $params = $params + ['action' => $action];
        }

        // Adding the 1 = 1 just to have the WHERE part so that all further conditions added by callers can be
        // appended to $sql with and ' AND'.
        $sql = 'SELECT ' . $select . '
                  FROM {' . $tablename . '} ' . $tablealias . '
                  ' . $usedanalysablesjoin . '
                  JOIN {context} ctx ON (ctx.contextlevel = :contextlevel AND ctx.instanceid = ' . $tablealias . '.id)
                  WHERE 1 = 1';

        return [$sql, $params];
    }