Improve SQL query performance accounting for other table records

1

I have an E-learning system and I need to create a report to return how many students were approved, disapproved, students who have not accessed for more than 30 days, students who are taking the course and those who have not yet attended.

The tables that were used are

'company'            Tabela de empresas;
'company_place'      Tabela de centro de treinamentos;
'user'               Tabela de usuários;
'user_course'        Tabela que armazena a inscrição de usuários nos cursos;
'user_course_lesson' Registra as aulas assistidas pelo usuário.

The rules are as follows

For approved students : must contain end date and field approved as 1;

For failed students : should contain end date and field approved as 0;

For drop-down students : With no end date, the field approved is 0, and the last update in the user_course_lesson table should be more than 30 days.

For students who have not yet logged in : There should be no attended class in the user_course_lesson table

For students with the course in progress : total of records without a completion date and with the field approved as 0, less students dropping out and students who did not.

The SQL that I'm using is as follows

SELECT
    /* Nome da empresa */
    c.'name',

    /* Alunos aprovados */
    SUM( case when uc.'approved' = "1" AND uc.'finished_at' IS NOT NULL then 1 else 0 end ) as approved,

    /* Alunos reprovados */
    SUM( case when uc.'approved' = "0" AND uc.'finished_at' IS NOT NULL then 1 else 0 end ) as reproved,

    /* Alunos desistentes */
    SUM( case when uc.'approved' = "0" AND uc.'finished_at' IS NULL AND (
        SELECT 
            MAX( case when 'updated_at' IS NULL then 'created_at' else 'updated_at' end ) as "last_access"
        FROM 'user_course_lesson' as cl
        WHERE cl.'user_course_id' = uc.'id'
        ORDER BY 'last_access' DESC
        LIMIT 1
    ) <= DATE_ADD(now(), INTERVAL(-30) DAY) then 1 else 0 end ) as dropout,

    /* Alunos que não acessaram ainda */
    SUM( case when uc.'approved' = "0" AND uc.'finished_at' IS NULL AND (
        SELECT 
            COUNT( cl.'id' ) as "access"
        FROM 'user_course_lesson' as cl
        WHERE cl.'user_course_id' = uc.'id'
    ) = 0 then 1 else 0 end ) as not_access,

    /* Alunos com o curso em andamento */
    SUM( case when uc.'approved' = "0" AND uc.'finished_at' IS NULL then 1 else 0 end ) - 
    SUM( case when uc.'approved' = "0" AND uc.'finished_at' IS NULL AND (
        SELECT 
            MAX( case when 'updated_at' IS NULL then 'created_at' else 'updated_at' end ) as "last_access"
        FROM 'user_course_lesson' as cl
        WHERE cl.'user_course_id' = uc.'id'
        ORDER BY 'last_access' DESC
        LIMIT 1
    ) <= DATE_ADD(now(), INTERVAL(-30) DAY) then 1 else 0 end ) -
    SUM( case when uc.'approved' = "0" AND uc.'finished_at' IS NULL AND (
        SELECT 
            COUNT( cl.'id' ) as "access"
        FROM 'user_course_lesson' as cl
        WHERE cl.'user_course_id' = uc.'id'
    ) = 0 then 1 else 0 end ) as pending,
    count( uc.'id' ) as 'total'
FROM 'company' as c

INNER JOIN 'company_place' as p
    ON p.'company_id' = c.'id'

INNER JOIN 'user' as u
    ON u.'place_id' = p.'id'

INNER JOIN 'user_course' as uc
    ON uc.'user_id' = u.'id';

The problem

SQL repeats many queries, and the time to calculate with few records is already high, around 0.056 seconds, with increasing records that time will increase exponentially.

Ideally, queries in the user_course_lesson table would not repeat, or have another way to write this SQL with JOINs or another form of relationship.

    
asked by anonymous 05.12.2014 / 21:40

1 answer

2

The solution had a reduction of four queries, however I had to include a LEFT JOIN , since I need the table to return the records of the user_course table even if it has no relationship with user_course_lesson . >

I have changed the syntax of CASE to IF just to improve reading.

Before the query took an average of 0.056 seconds, now it has decreased to 0.006 seconds.

SELECT
    /* Nome da empresa */
    c.'name',

    /* Alunos aprovados */
    SUM( IF( uc.'approved' = "1" AND uc.'finished_at' IS NOT NULL, 1, 0 ) ) as approved,

    /* Alunos reprovados */
    SUM( IF( uc.'approved' = "0" AND uc.'finished_at' IS NOT NULL, 1, 0 ) ) as reproved,

    /* Alunos desistentes */
    SUM( IF( uc.'approved' = "0" AND uc.'finished_at' IS NULL AND uc.'date' <= DATE_ADD(now(), INTERVAL(-30) DAY), 1, 0 ) ) as dropout,

    /* Alunos que não acessaram ainda */
    SUM( IF( uc.'approved' = "0" AND uc.'finished_at' IS NULL AND uc.'date' IS NULL, 1, 0 ) ) as not_access,

    /* Alunos com o curso em andamento */
    SUM( IF( uc.'approved' = "0" AND uc.'finished_at' IS NULL, 1, 0 ) ) - 
    SUM( IF( uc.'approved' = "0" AND uc.'finished_at' IS NULL AND uc.'date' <= DATE_ADD(now(), INTERVAL(-30) DAY), 1, 0 ) ) -
    SUM( IF( uc.'approved' = "0" AND uc.'finished_at' IS NULL AND uc.'date' IS NULL, 1, 0 ) ) as pending,

    /* Total */
    count( uc.'id' ) as 'total'
FROM 'company' as c

INNER JOIN 'company_place' as p
    ON p.'company_id' = c.'id'

INNER JOIN 'user' as u
    ON u.'place_id' = p.'id'

INNER JOIN (
    SELECT 
        uc.'id',
        uc.'user_id',
        uc.'approved',
        uc.'finished_at',
        MAX( IF( cl.'updated_at' IS NULL, cl.'created_at', cl.'updated_at' ) ) as "date"
    FROM 'user_course' as uc
    LEFT JOIN 'user_course_lesson' as cl
    ON cl.'user_course_id' = uc.'id'
    GROUP BY uc.'id'
)  as uc
ON uc.'user_id' = u.'id'

GROUP BY c.'id';
    
06.12.2014 / 08:30