Relatorios personalizados desenvolvidos pela comunidade

From MoodleDocs

Introdução

Os modelos de relatórios disponibilizados aqui foram desenvolvidos pela comunidade. Todos são livres para contribuir. Se você tiver dúvidas de trabalho que acha que seriam úteis para os membros da comunidade, compartilhe-as aqui.

Plugins de relatório

Há dois plugins de relatório comumente utilizados no Moodle para criar e exibir relatórios SQL:

Você pode instalar e usar qualquer um desses plugins de relatório em seu site Moodle. Se seu foro é mais no envio de relatórios por e-mail, então verifique primeiro o” Ad-hoc database queries”. Se você deseja compartilhar relatórios com professores e estudantes em cursos, então veja o plugin “Configurable Reports”.

Algumas notas técnicas para usuários de relatórios

Estrutura de tabelas e schema

Se você está procurando alguma ajuda para entender as tabelas e estrutura do banco de dados, você pode encontrar nesta página um esquema de banco de dados atualizado, graças ao Marcus Green.

Nomes das tabelas e prefix_

Você verá que muitas das consultas abaixo usam "prefix_" para iniciar nomes de tabelas, por exemplo “prefix_user”. Este “prefix_” é usado antes dos nomes das tabelas quando você está usando um dos dois plugins, relatórios “Ad-hoc” ou “Configurable Reports”. Esses dois plugins substituem “prefix_” pelo prefixo real definido em seu arquivo de configuração do Moodle “config.php”. Isso ocorre porque, embora o prefixo padrão da tabela seja “mdl_”, ao instalar o Moodle, você pode definir qualquer prefixo que desejar, portanto, é personalizável.

As consultas escritas para serem usadas com qualquer um desses dois plugins devem sempre ter “prefix_” para iniciar todos os nomes de tabelas em vez do prefixo real.

Quando você usa ferramentas como PhpMyAdmin, Workbench, etc., você deve usar os nomes das tabelas reais como estão no banco de dados, incluindo o prefixo real definido para seu próprio site.

Portanto:

  • Se uma consulta abaixo tiver "prefix_" em seus nomes de tabela e você quiser usar essa consulta em uma ferramenta como phpMyAdmin, será necessário substituí-los pelo prefixo real das tabelas do seu site;
  • Se uma consulta abaixo tiver "mdl_" ou algum outro prefixo em seus nomes de tabela e você estiver usando isso em qualquer um dos dois plugins do Moodle, será necessário substituir esses prefixos explícitos por "prefix_".

Você receberá um erro "Sem prefixo explícito" em qualquer um desses plugins se usar código com o prefixo da tabela real nele.

Sobre %%FILTER_ e variáveis similares

Você verá várias consultas com variáveis que começam e terminam com dois “%%” sinais de porcentagem, por exemplo “%%WWWROOT%%” e vários deles que começam com “%%FILTER_”. Essas variáveis são específicas para um ou ambos os plugins do Moodle. Eles não são código SQL padrão e não funcionarão em código feito em outras ferramentas fora desses dois plugins.

Além disso, embora esses dois plugins compartilhem parte da variável, alguns funcionam apenas em um ou outro plugin. Por exemplo, “%%WWWROOT%%” funciona em ambos; as variáveis “%%C%%”, “%%S%%” e “%%Q%%” só funcionam no plugin de consultas de banco de dados Ad-hoc; e aqueles que iniciam “%%FILTER_” funcionam apenas no plugin Relatórios Configuráveis (Configurable Reports). Você receberá um erro de sintaxe se usar uma variável que o plugin não suporta.

Encontre mais detalhes na seção sobre Variáveis no final da página.

Sobre formatação de datas

As datas e horas no Moodle são armazenadas internamente no banco de dados como o timestamp Unix Epoch, que é o número de segundos desde 1º de janeiro de 1970 às 00:00 UTC, que no banco de dados será representado como 0.

Para convertê-los em formatos "legíveis por humanos" que usamos todos os dias, você precisará fazer isso em sua consulta com código. Existem vários exemplos nos relatórios nesta página: uma pesquisa nesta página por "FROM_UNIXTIME" mostrará como usar e converter as datas. Observe que quando os horários são convertidos em data padrão e os horários dos carimbos de data/hora estão no formato UTC e você pode querer convertê-los em um fuso horário local.

As funções “FROM_UNIXTIME” e “DATE_FORMAT” que são comuns nos relatórios abaixo são funções específicas do MySQL, que você pode usar em sites MySQL e MariaDB.

Para outra versão do SQL, você precisará editar essas consultas para que funcionem corretamente. Para PostgreSQL, consulte a página de manual em funções de formatação de tipo de dados. Há vários exemplos específicos do PostgreSQL entre as consultas abaixo.

Existem muitos formatos de data e hora usados em vários países, mas todos podem ser formatados com base no valor do registro de data e hora original. Para obter uma amostra em MySQL, você pode usar a seguinte consulta para mostrar a data de instalação do seu site Moodle em vários formatos:

SELECT
    l.timecreated AS "Timestamp",
    DATE_FORMAT(FROM_UNIXTIME(l.timecreated), '%d/%m/%Y') AS "Formato padrão no mundo",
    DATE_FORMAT(FROM_UNIXTIME(l.timecreated), '%m/%d/%Y') AS "Formato EUA",
    DATE_FORMAT(FROM_UNIXTIME(l.timecreated), '%Y-%m-%d') AS "Formato padrão ISO",
    DATE_FORMAT(FROM_UNIXTIME(l.timecreated), '%Y-%m-%d %H:%i') AS "Formato padrão ISO com tempo em 24 horas"
FROM
    prefix_logstore_standard_log l
WHERE
    l.id = 1

Veja a documentação do MySQL para todas as opções possíveis de formatação de data.

Nota: para timestamps que são 0, isso às vezes representa "Nunca" ou "Sem data" para alguns campos no Moodle. Por exemplo, se o primeiro login de um usuário for 0, isso significa que ele nunca fez login, não que seu primeiro login tenha sido em 1º de janeiro de 1970 (já que o Moodle ainda não existia). Se você deseja distinguir essas datas em sua saída, pode usar uma condicional para testar o 0, por exemplo:

IF(
    u.firstaccess = 0,
    "Nunca",
    DATE_FORMAT(FROM_UNIXTIME(u.firstaccess), '%d-%m-%Y %H:%i')
) AS "Primeiro acesso",

Isso mostrará "Nunca" quando o "timestamp" for igual a "0" ou a data formatada corretamente, permitindo que você classifique a coluna facilmente.

Sugestões para Colaboradores

As sugestões a seguir tornariam suas contribuições mais úteis.

Controle de versão
  • Apenas poste consultas verificadas que funcionem em versões suportadas pelo Moodle;
  • Por favor, não poste versões de consultas para versões antigas e fora de suporte;
  • Identifique no início de cada consulta o banco de dados compatível (PostgreSQL, MariaDB, etc.); e
  • Por favor, remova qualquer consulta que você escreveu que não funcione mais na versão atual do Moodle: especialmente em casos de alterações estruturais em tabelas que tornam consultas antigas quebradas (por exemplo, alterações nos Logs em 2.7, Mensagens em 3.6, etc.)
Adicionando anotações para outros
  • Se você estiver usando uma linguagem SQL diferente do MySQL, sinalize quaisquer funções específicas ou outras peculiaridades presentes;
  • Considere colocar seu nome e/ou um link como "Contribuído por";
  • Considere adicionar uma breve descrição do que seu relatório faz e sua saída esperada;
  • Se o código incluir plugins de terceiros não padrão, observe especificamente em sua descrição;
  • Se o seu código incluir qualquer nome de domínio, curso ou nome de usuário ou outras referências específicas ao seu site, considere torná-los mais genéricos;
  • Por favor, use o português, pois esta é a versão em português da documentação do Moodle; e
  • Se houver um debate ou outras informações sobre a consulta de uma discussão no fórum, sinta-se à vontade para adicionar um link referenciando a discussão.
Aliases de tabela comumente usados

Ao longo dos anos, surgiram algumas convenções comuns para aliases de tabelas. Você os verá usados frequentemente nesses relatórios.

Se você usar esses aliases, suas consultas serão mais fáceis de serem lidas por outras pessoas. Mas é claro, qualquer pseudônimo legal pode ser usado.

  • c = prefix_course
  • cat ou cc = prefix_course_categories
  • cm = prefix_course_modules
  • ctx or con = prefix_context
  • e = prefix_enrol
  • f = prefix_files
  • l ou log = prefix_logstore_standard_log
  • m = prefix_module
  • r = prefix_role
  • ra = prefix_role_assignments
  • u = prefix_user
  • ue = prefix_user_enrolments

Relatórios

Relatórios Administrativos

Acessos únicos ao Moodle

Contribuído por: Anderson Blaine

Este relatório considera acesso únicos como qualquer ação de “visualização” de usuários distintos (contagem de "username"). Limitado ao período de 30 dias.

Observação: O Moodle registra a ação de "loggedin", mas esse parâmetro pode gerar inconsistências no relatório de acessos únicos por dia, uma vez que dependendo da configuração do Moodle o usuário pode permanecer autenticado por mais de um dia (no caso do Moodle App o padrão é 12 semanas) e por isso foi considerado a ação de visualização e não "login".

PostgreSQL

SELECT
    TO_CHAR(TO_TIMESTAMP(l.timecreated), 'DD/MM/YYYY') AS "Data",
    COUNT (DISTINCT u.username) AS "Acessos"
FROM
    prefix_logstore_standard_log AS l
    JOIN prefix_user AS u ON l.userid = u.id
WHERE
    l.action = 'viewed'
    AND TO_TIMESTAMP(l.timecreated) >= NOW() - interval '30 days'
GROUP BY
    "Data"

Acessos únicos ao Moodle com dados de campo de perfil do usuário

Contribuído por: Anderson Blaine

Este relatório considera acesso únicos como qualquer ação de “visualização” de usuários distintos (contagem de "username"). Limitado ao período de 30 dias.

No exemplo abaixo foi adicionado uma "subquery" para adicionar um campo de perfil personalizado de nome "Pólo". Você pode alterar para qualquer valor de "shortname" (nome curto do campo).

PostgreSQL

SELECT
    TO_CHAR(TO_TIMESTAMP(l.timecreated), 'DD/MM/YYYY') AS "Data",
    (
        SELECT
            d1.data
        FROM
            prefix_user_info_data AS d1
            JOIN prefix_user_info_field AS f1 ON d1.fieldid = f1.id
            AND f1.shortname = 'polo'
        WHERE
            d1.userid = u.id
    ) AS "Pólo",
    COUNT (DISTINCT u.username) AS "Acessos"
FROM
    prefix_logstore_standard_log AS l
    JOIN prefix_user AS u ON l.userid = u.id
WHERE
    l.action = 'viewed'
    AND TO_TIMESTAMP(l.timecreated) >= NOW() - interval '30 days'
GROUP BY
    "Data",
    "Pólo"

Dica: A pesquisa por texto é sempre mais lenta que a pesquisa por "chave primária", logo, recomendo trocar o "shortname" pelo "id" da tabela "prefix_user_info_field". No exemplo acima, a variável F1.SHORTNAME = 'polo' uma vez otimizada seria 'F1.ID = '1'. O número "1" corresponde a chave primária do campo de perfil "Pólo", para saber qual chave utilizar você deve checar a tabela "prefix_user_info_field".

Erros de acesso, motivo: usuário inexistente

Contribuído por: Anderson Blaine

O Moodle registra em seu log as tentativas de acesso malsucedidas e este relatório lista, dentro do intervalo de 30 dias, os acessos não concluídos devido a inexistência de cadastro ou erro de preenchimento dos dados de login.

PostgreSQL

SELECT
    JSON_EXTRACT_PATH_TEXT(OTHER :: JSON, 'username') AS "username",
    TO_CHAR(TO_TIMESTAMP(l.timecreated), 'DD/MM/YYYY HH:MM') AS "Data"
FROM
    prefix_logstore_standard_log AS l
WHERE
    l.action = 'failed'
    AND l.target = 'user_login'
    AND l.other like '%reason":1%'
    AND TO_TIMESTAMP(l.timecreated) >= NOW() - interval '30 days'
ORDER BY
    l.timecreated DESC

Erros de acesso, motivo: senha inválida

Contribuído por: Anderson Blaine

Este relatório lista os usuários válidos, que nunca acessaram a plataforma devido a senha inválida. No início de semestre ou cadastro recém realizado, para ações proativas, o suporte poderá agir entrando em contato com o usuário.

PostgreSQL

SELECT
    DISTINCT u.username AS "Usuário",
    CONCAT (u.firstname, ' ', u.lastname) AS "Nome completo",
    CONCAT(
        '<a target="_new" href="%%WWWROOT%%/user/profile.php?id=',
        l.userid,
        '">Acessar Perfil</a>'
    ) AS "Perfil",
    l.target,
    l.action
FROM
    prefix_logstore_standard_log AS l
    JOIN prefix_user AS u ON u.id = l.userid
WHERE
    l.action LIKE 'failed'
    AND l.target LIKE 'user_login'
    AND TO_TIMESTAMP(l.timecreated) >= NOW() - interval '7 days'
    AND u.lastaccess = 0

Inscrições vs. Conclusões mensais por curso

Contribuído por: Anderson Blaine

Este relatório apresenta o total de estudantes inscritos e concluintes por mês, agrupados por curso.

Observações: Cursos com duração superior a 30 dias podem ter o número de conclusões superior ao número de inscritos no mês. Exemplo: Janeiro ocorreram 100 inscrições, em fevereiro outras 20 inscrições. Mas o número de conclusões de curso em fevereiro foi de 60 (soma das conclusões, independente da data de inscrição). Portanto, os dados podem flutuar conforme a movimentação natural de inscrições e conclusões.

PostgreSQL

SELECT
	CONCAT('<a target="_new" href="%%WWWROOT%%/course/view.php?id=', c.id, '">', c.shortname, '</a>') AS "Curso",
	count(DISTINCT ra.id) AS "Inscrições",
	count(DISTINCT cc.timecompleted) AS "Conclusões",
	to_char(date_trunc('month', to_timestamp(ra.timemodified)), 'MM/YYYY') AS "Data"
FROM
	prefix_course c
JOIN prefix_context cx ON
	c.id = cx.instanceid
JOIN prefix_role_assignments ra ON
	cx.id = ra.contextid
JOIN prefix_user u ON
	ra.userid = u.id
JOIN prefix_role r ON
	ra.roleid = r.id
LEFT JOIN prefix_course_completions cc ON
	c.id = cc.course
	AND ra.userid = cc.userid
WHERE
	cx.contextlevel = 50
	AND r.shortname = 'student'
GROUP BY
	"Curso",
	"Data"
ORDER BY
	"Curso",
	"Data"

Usuários criados: Contagem por autor do cadastro

Contribuído por: Anderson Blaine

Este relatório lista todos os autores de novos cadastros de usuários no site e realiza a contagem de quantos foram criados por autor.

PostgreSQL/MariaDB

SELECT
    CONCAT(u2.firstname, ' ', u2.lastname) AS "Autor dos cadastros",
    COUNT(DISTINCT u.username) AS "Usuários criados"
FROM
    prefix_logstore_standard_log AS l
    JOIN prefix_user AS u ON u.id = l.relateduserid
    JOIN prefix_user AS u2 ON u2.id = l.userid
WHERE
    l.action = 'created'
    AND l.target = 'user'
GROUP BY
    "Autor dos cadastros"
ORDER BY
    "Usuários criados" DESC

Usuários criados: Últimos 30 dias

Contribuído por: Anderson Blaine

Lista os usuários criados nos últimos 30 dias, o autor do cadastro e a origem da solicitação. A "origem" é uma coluna útil para verificar de onde partiu o cadastro, por exemplo, pela "web" quando um usuário administrador cadastrou pela administração do site ou "ws" para webservice, sinalizando que o cadastro pode ter sido realizado pela integração de um software de terceiros ao Moodle.

PostgreSQL

SELECT
    u.username AS "Usuário",
    CONCAT(u.firstname, ' ', u.lastname) AS "Nome completo",
    CONCAT(u2.firstname, ' ', u2.lastname) AS "Autor do cadastro",
    l.origin AS "Origem",
    TO_CHAR(TO_TIMESTAMP(l.timecreated), 'DD/MM/YYYY HH:MM') AS "Data de criação"
FROM
    prefix_logstore_standard_log AS L
    JOIN prefix_user AS u ON u.id = l.relateduserid
    JOIN prefix_user AS u2 ON u2.id = l.userid
WHERE
    l.action = 'created'
    AND l.target = 'user'
    AND TO_TIMESTAMP(l.timecreated) >= NOW() - interval '30 days'
ORDER BY
    l.timecreated DESC

Relatórios de Curso

Funil de conclusão de curso / Relatório de evasão

Contribuído por: Anderson Blaine

Este relatório de curso apresenta um funil de ações esperadas até a conclusão efetiva do curso pelo aluno, respondendo as seguintes questões:

  • O aluno acessou o curso?
  • A quanto tempo ele não acessa o curso?
  • Quantas atividades com acompanhamento de conclusão foram finalizadas?
  • Qual a nota atual no curso?
  • Qual a data de conclusão?


Observação: Outros papéis como professor, moderador, etc. foram intencionalmente adicionados neste relatório. Também foram incluídos os grupos para facilitar a gestão.

PostgreSQL

SELECT
    CONCAT('<a target="_new" href="%%WWWROOT%%/course/view.php?id=', c.id,'">',c.shortname,'</a>') AS "Curso",
    CONCAT('<a target="_new" href="%%WWWROOT%%/user/profile.php?id=', u.id,'&course=',c.id,'">',u.firstname, ' ', u.lastname,'</a>') AS "Perfil",
    u.username AS "Identificação de usuário",
    u.email AS "E-mail",
(
   	 SELECT
   		 ARRAY_TO_STRING(array_agg(r.shortname), ' | ')
   	 	FROM prefix_course AS c
   	 	JOIN prefix_context AS ctx ON c.id = ctx.instanceid
   	 	JOIN prefix_role_assignments AS ra ON ra.contextid = ctx.id
   	 	JOIN prefix_role AS r ON r.id = ra.roleid
   	 	JOIN prefix_user AS u ON u.id = ra.userid
   	 	WHERE c.id = e.courseid AND u.id = ue.userid
) AS "Papel(eis)",
(
    SELECT
        ARRAY_TO_STRING(array_agg(g.name), ' | ')
    	FROM
       	prefix_course AS c
   	 	JOIN prefix_groups AS g ON g.courseid = c.id
   	 	JOIN prefix_groups_members AS m ON g.id = m.groupid
   	 	JOIN prefix_user AS u ON m.userid = u.id
   	 	WHERE c.id = e.courseid AND u.id = ue.userid
	) AS "Grupo(s)",
    CASE ue.status
     	WHEN 0 THEN 'Ativo'
     	WHEN 1 THEN 'Suspenso'
    END AS "Estado da inscrição",
    CASE ue.timestart
        WHEN 0 THEN 'N/I'
        ELSE to_char(to_timestamp(ue.timestart), 'DD/MM/YYYY')
    END AS "Inscrição começa",
    CASE
    	WHEN ul.timeaccess IS NULL THEN 'Nunca acessou'
    	ELSE to_char(to_timestamp(ul.timeaccess), 'DD/MM/YYYY')
    END AS "Data do último acesso",
    DATE_PART('day', CURRENT_TIMESTAMP - to_timestamp(ul.timeaccess)::TIMESTAMP) AS "Dias sem acessar",
(
	SELECT
		COUNT(cm.course)
		FROM prefix_course_modules AS cm
		WHERE cm.course = c.id AND cm.completion <> 0
	) AS "Atividades c/ conclusão ativa",
(
	SELECT
		COUNT(cmc.coursemoduleid)
		FROM prefix_course_modules AS cm
		LEFT JOIN prefix_course_modules_completion AS cmc ON cmc.coursemoduleid = cm.id
		WHERE cm.course = c.id AND cm.completion <> 0 AND cmc.userid = ue.userid
	) AS "Atividades concluídas",
(
	SELECT 
    	ROUND(gg.finalgrade, 2) 
    	FROM prefix_grade_grades AS gg
    	JOIN prefix_grade_items AS gi ON gi.id = gg.itemid
    	WHERE gg.userid = u.id AND gi.itemtype = 'course' AND gi.courseid = c.id
    ) AS "Nota no curso",
(
	SELECT
		ROUND(gi.grademax, 2)
    	FROM prefix_grade_grades AS gg
    	JOIN prefix_grade_items AS gi ON gi.id = gg.itemid
    	WHERE gg.userid = u.id AND gi.itemtype = 'course' AND gi.courseid = c.id
	 ) AS "Total do curso",
     CASE
     	WHEN cc.timecompleted IS NULL THEN 'Não concluído'
     	ELSE to_char(to_timestamp(cc.timecompleted), 'DD/MM/YYYY HH24:MI')
     END AS "Conclusão do curso"
FROM
    prefix_user_enrolments AS ue
    JOIN prefix_enrol AS e ON e.id = ue.enrolid
    JOIN prefix_course AS c ON c.id = e.courseid
    JOIN prefix_user AS u ON u.id = ue.userid
    LEFT JOIN prefix_user_lastaccess as ul ON u.id = ul.userid AND c.id = ul.courseid
    LEFT JOIN prefix_course_completions AS cc ON cc.course = c.id AND cc.userid = u.id
WHERE
    c.id = %%COURSEID%%

Módulos de Atividades

Questionário

Sobreposições de usuários e grupos em questionários

Contribuído por: Anderson Blaine e Leandro Engler Bocon

Lista todas as sobreposições de data de início, de encerramento e tentativas permitidas na atividade Questionário para usuários e grupos.

Observações: Este relatório irá aprecer em branco caso não haja sobreposições no curso. Para saber qual a ID do módulo "Quiz" crie um relatório simples com a query "select * from prefix_modules".

MariaDB

SELECT
	CONCAT('<a target="_new" href="%%WWWROOT%%/course/view.php?id=',c.id,'">',c.shortname,'</a>') AS "Curso",
    CONCAT('<a target="_new" href="%%WWWROOT%%/mod/quiz/view.php?id=',cm.id,'">',q.name,'</a>') AS "Questionário",
    IF (q.attempts = 0, "Ilimitado", q.attempts) AS "Tentativas permitidas",
    ROUND(q.grade,0) AS "Nota máxima",
    IF (q.timeopen = 0, "Não definida", DATE_FORMAT(FROM_UNIXTIME(q.timeopen), '%d/%m/%Y %H:%i')) AS "Data de abertura",
    IF (q.timeclose = 0,"Não definida", DATE_FORMAT(FROM_UNIXTIME(q.timeclose), '%d/%m/%Y %H:%i')) AS "Data de encerramento",
	CASE
    	WHEN (SELECT qo.groupid IS NOT NULL) THEN (SELECT CONCAT('Grupo: ','<a target="_new" href="%%WWWROOT%%/mod/quiz/overrides.php?cmid=',cm.id,'&mode=group','">',g.name, '</a>'))
    	ELSE CONCAT('Aluno: ','<a target="_new" href="%%WWWROOT%%/mod/quiz/overrides.php?cmid=',cm.id,'&mode=user','">', u.firstname, ' ',u.lastname, '</a>')
    END AS "Sobreposições",
    IF (qo.attempts IS NULL, "Sem sobreposição", qo.attempts) AS "Sobreposição: Tentativas permitidas",
    IF (qo.timeopen IS NULL, "Sem sobreposição", DATE_FORMAT(FROM_UNIXTIME(qo.timeopen), '%d/%m/%Y %H:%i')) AS "Sobreposição: Data de abertura",
    IF (qo.timeclose IS NULL, "Sem sobreposição", DATE_FORMAT(FROM_UNIXTIME(qo.timeclose), '%d/%m/%Y %H:%i')) AS "Sobreposição: Data de encerramento"
FROM
   prefix_quiz AS q
   JOIN prefix_course_modules AS cm ON cm.instance = q.id AND cm.module = 32 /* id do módulo quiz na tabela prefix_modules. */
   JOIN prefix_course AS c ON q.course = c.id
   JOIN prefix_course_categories AS cat on c.category = cat.id
   JOIN prefix_quiz_overrides AS qo ON q.id = qo.quiz
   LEFT JOIN prefix_user as u ON u.id = qo.userid
   LEFT JOIN prefix_groups AS g ON g.id = qo.groupid

PostgreSQL

SELECT
    CONCAT('<a target="_new" href="%%WWWROOT%%/course/view.php?id=', c.id,'">',c.shortname,'</a>') AS "Curso",
    CONCAT('<a target="_new" href="%%WWWROOT%%/mod/quiz/view.php?id=',cm.id,'">',q.name,'</a>') AS "Questionário",
    CASE
        WHEN q.attempts = 0 THEN 'Ilimitado'
        ELSE q.attempts :: text
    END AS "Tentativas permitidas",
    CASE
        WHEN q.timeopen = 0 THEN 'Não definida'
        ELSE to_char(to_timestamp(q.timeopen), 'DD/MM/YYYY HH24:MI')
    END AS "Data de abertura",
    CASE
        WHEN q.timeclose = 0 THEN 'Não definida'
        ELSE to_char(to_timestamp(q.timeclose), 'DD/MM/YYYY HH24:MI')
    END AS "Data de encerramento",
    CASE
        WHEN (SELECT qo.groupid IS NOT NULL) THEN (SELECT CONCAT('Grupo: ','<a target="_new" href="%%WWWROOT%%/mod/quiz/overrides.php?cmid=',cm.id,'&mode=group','">',g.name,'</a>')
        )
        ELSE CONCAT('Aluno: ','<a target="_new" href="%%WWWROOT%%/mod/quiz/overrides.php?cmid=',cm.id,'&mode=user','">',u.firstname,' ',u.lastname,'</a>')
    END AS "Sobreposições",
    CASE
        WHEN qo.attempts IS NULL THEN 'Sem sobreposição'
        ELSE qo.attempts :: text
    END AS "Sobreposição: Tentativas permitidas",
    CASE
        WHEN qo.timeopen IS NULL THEN 'Sem sobreposição'
        ELSE to_char(to_timestamp(qo.timeopen), 'DD/MM/YYYY HH24:MI')
    END AS "Sobreposição: Data de abertura",
    CASE
        WHEN qo.timeclose IS NULL THEN 'Sem sobreposição'
        ELSE to_char(to_timestamp(qo.timeclose), 'DD/MM/YYYY HH24:MI')
    END AS "Sobreposição: Data de encerramento"
FROM
    prefix_quiz AS q
    JOIN prefix_course_modules AS cm ON cm.instance = q.id AND cm.module = 17  /* id do módulo quiz na tabela prefix_modules. */
    JOIN prefix_course AS c ON q.course = c.id
    JOIN prefix_course_categories AS cat on c.category = cat.id
    JOIN prefix_quiz_overrides AS qo ON q.id = qo.quiz
    LEFT JOIN prefix_user as u ON u.id = qo.userid
    LEFT JOIN prefix_groups AS g ON g.id = qo.groupid

Anexos: Dicas úteis