Otimizando uma consulta de junção de 3 tabelas
-
26-09-2020 - |
Pergunta
SELECT job.id_job, job.fk_company, job.fk_user, job.fk_job_category,
job.fk_job_type, job.fk_place, job.job_identifier, job.external_job_id, job.title,
job.short_description, job.status, job.expires_at, job.nb_vacancies,
job.featured_rank, job.created_at, job.updated_at, job.deleted_at, job.slug,
COUNT(fk_job) AS `application_count`
FROM `job`
INNER JOIN `company` ON (job.fk_company=company.id_company)
LEFT JOIN `job_application` ON (job.id_job=job_application.fk_job)
WHERE job.deleted_at IS NULL
GROUP BY job.id_job
ORDER BY job.id_job DESC
LIMIT 50
Esta consulta demora um pouco, embora todos os campos que usamos no JOIN
, GROUP BY
e ORDER BY
declarações são indexadas ou declaradas como foreign key
ou primary key
.
+----+-------------+-----------------+-------+----------------------+----------------------+---------+----------------------------+------+----------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-----------------+-------+----------------------+----------------------+---------+----------------------------+------+----------------------------------------------+
| 1 | SIMPLE | company | index | PRIMARY | company_I_2 | 4 | NULL | 3244 | Using index; Using temporary; Using filesort |
| 1 | SIMPLE | job | ref | job_FI_1 | job_FI_1 | 4 | intjobs.company.id_company | 3 | Using where |
| 1 | SIMPLE | job_application | ref | job_application_FI_2 | job_application_FI_2 | 4 | intjobs.job.id_job | 1 | Using index |
+----+-------------+-----------------+-------+----------------------+----------------------+---------+----------------------------+------+----------------------------------------------+
3 rows in set (0.00 sec)
SHOW CREATE TABLE job;
CREATE TABLE `job` (
`id_job` int(11) NOT NULL AUTO_INCREMENT,
`fk_company` int(11) NOT NULL,
`fk_user` int(11) NOT NULL,
`fk_job_category` int(11) NOT NULL,
`fk_job_type` int(11) NOT NULL,
`fk_place` int(11) DEFAULT NULL,
`job_identifier` varchar(20) DEFAULT NULL,
`external_job_id` varchar(50) DEFAULT NULL,
`title` varchar(255) DEFAULT NULL,
`short_description` text,
`status` enum('new','active_expiration_reminded','active','inactive','expired') NOT NULL DEFAULT 'new',
`expires_at` datetime DEFAULT NULL,
`nb_vacancies` int(11) NOT NULL DEFAULT '1',
`featured_rank` int(11) NOT NULL DEFAULT '0',
`created_at` datetime DEFAULT NULL,
`updated_at` datetime DEFAULT NULL,
`deleted_at` datetime DEFAULT NULL,
`slug` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id_job`),
UNIQUE KEY `job_U_1` (`job_identifier`),
UNIQUE KEY `job_slug` (`slug`),
KEY `job_FI_1` (`fk_company`),
KEY `job_FI_2` (`fk_user`),
KEY `job_FI_3` (`fk_job_category`),
KEY `job_FI_4` (`fk_job_type`),
KEY `job_FI_6` (`fk_place`),
KEY `job_FI_5` (`fk_place`),
CONSTRAINT `job_FK_1` FOREIGN KEY (`fk_company`) REFERENCES `company` (`id_company`),
CONSTRAINT `job_FK_2` FOREIGN KEY (`fk_user`) REFERENCES `user` (`id_user`),
CONSTRAINT `job_FK_3` FOREIGN KEY (`fk_job_category`) REFERENCES `job_category` (`id_job_category`),
CONSTRAINT `job_FK_4` FOREIGN KEY (`fk_job_type`) REFERENCES `job_type` (`id_job_type`),
CONSTRAINT `job_FK_5` FOREIGN KEY (`fk_place`) REFERENCES `place` (`id_place`)
) ENGINE=InnoDB AUTO_INCREMENT=27630 DEFAULT CHARSET=utf8 |
SHOW CREATE TABLE company;
CREATE TABLE `company` (
`id_company` int(11) NOT NULL AUTO_INCREMENT,
`fk_place` int(11) DEFAULT NULL,
`fk_industry` int(11) DEFAULT NULL,
`fk_modified_by_user` int(11) DEFAULT NULL,
`name` varchar(255) NOT NULL,
`featured_rank` int(11) NOT NULL DEFAULT '0',
`created_at` datetime DEFAULT NULL,
`updated_at` datetime DEFAULT NULL,
`deleted_at` datetime DEFAULT NULL,
`slug` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id_company`),
UNIQUE KEY `company_slug` (`slug`),
KEY `company_I_1` (`name`),
SHOW CREATE TABLE job_application;
CREATE TABLE `job_application` (
`id_job_application` int(11) NOT NULL AUTO_INCREMENT,
`fk_user` int(11) NOT NULL,
`fk_job` int(11) NOT NULL,
`status` enum('new','application_sent','approval_pending','in_call','invalid','jobseeker_notified','improvement_pending','read_pending','application_read','read_pending_reminded','manual_interaction_pending') NOT NULL DEFAULT 'new',
`created_at` datetime DEFAULT NULL,
`updated_at` datetime DEFAULT NULL,
`deleted_at` datetime DEFAULT NULL,
PRIMARY KEY (`id_job_application`),
KEY `job_application_FI_1` (`fk_user`),
KEY `job_application_FI_2` (`fk_job`),
CONSTRAINT `job_application_FK_1` FOREIGN KEY (`fk_user`) REFERENCES `user` (`id_user`),
CONSTRAINT `job_application_FK_2` FOREIGN KEY (`fk_job`) REFERENCES `job` (`id_job`)
) ENGINE=InnoDB AUTO_INCREMENT=29 DEFAULT CHARSET=utf
Versão MySQL:
+-------------------------+------------------------------+
| Variable_name | Value |
+-------------------------+------------------------------+
| innodb_version | 5.6.22 |
| protocol_version | 10 |
| slave_type_conversions | |
| version | 5.6.22-log |
| version_comment | MySQL Community Server (GPL) |
| version_compile_machine | x86_64 |
| version_compile_os | Linux |
+-------------------------+------------------------------+
Solução
Três pontos que a consulta pode ser melhorada:
- remova a junção para
company
.A condição de união é através de uma chave estrangeira não anulável, portanto deve ser sempre verdadeira. - mudar o
GROUP BY x ORDER BY x DESC
para:GROUP BY x DESC
.Isso evitará classificações desnecessárias.Observe que a sintaxe está obsoleta, então você pode precisar alterá-la novamente em uma atualização futura do mysql. - adicione um índice em
(deleted_at, id_job)
.Isso é essencial para esta consulta.Em geral, um índice em uma coluna de sinalização (verdadeiro/falso) ou que tenha poucos valores distintos geralmente é inútil.Mas odeleted_at
coluna é um carimbo de data/hora, portanto o índice também pode ser útil em outras consultas.Esta consulta tem o extraORDER BY / LIMIT
, para que o índice de 2 colunas possa ser usado de forma eficaz.
A consulta reescrita:
SELECT job.id_job, job.fk_company, job.fk_user, job.fk_job_category,
job.fk_job_type, job.fk_place, job.job_identifier, job.external_job_id, job.title,
job.short_description, job.status, job.expires_at, job.nb_vacancies,
job.featured_rank, job.created_at, job.updated_at, job.deleted_at, job.slug,
COUNT(fk_job) AS application_count
FROM job
LEFT JOIN job_application ON job.id_job = job_application.fk_job
WHERE job.deleted_at IS NULL
GROUP BY job.id_job DESC
LIMIT 50 ;
Você também pode usar esta versão, que força o uso do índice e evita o GROUP BY
na consulta principal (agrupa na subconsulta in-line que será executada apenas para 50 valores):
SELECT job.id_job, job.fk_company, job.fk_user, job.fk_job_category,
job.fk_job_type, job.fk_place, job.job_identifier, job.external_job_id, job.title,
job.short_description, job.status, job.expires_at, job.nb_vacancies,
job.featured_rank, job.created_at, job.updated_at, job.deleted_at, job.slug,
( SELECT COUNT(*)
FROM job_application AS ja
WHERE j.id_job = ja.fk_job
) AS application_count
FROM
( SELECT id_job
FROM job
WHERE deleted_at IS NULL
ORDER BY id_job DESC
LIMIT 50
) AS j
JOIN job ON job.id_job = j.id_job
ORDER BY j.id_job DESC ;
Licenciado em: CC-BY-SA com atribuição
Não afiliado a dba.stackexchange