O problema é que o agrupamento por
name
faz você perder o sales_id
informações, portanto o MySQL é forçado a usar uma tabela temporária. Embora não seja a solução mais limpa e uma das minhas abordagens menos favoritas, você pode adicionar um novo índice, em ambos o
name
e o sales_id
colunas, como:ALTER TABLE `yourdb`.`ycs_products`
ADD INDEX `name_sales_id_idx` (`name` ASC, `sales_id` ASC);
e forçar a consulta para usar este índice, com
force index
ou use index
:SELECT SQL_NO_CACHE p.name, COUNT(1) FROM ycs_sales s
INNER JOIN ycs_products p use index(name_sales_id_idx) ON s.id = p.sales_id
WHERE s.dtm BETWEEN '2018-02-16 00:00:00' AND '2018-02-22 23:59:59'
GROUP BY p.name;
Minha execução relatou apenas "usando where; using index" na tabela p e "usando where" na tabela s.
De qualquer forma, sugiro fortemente que você repense seu esquema, porque provavelmente você poderá encontrar um design melhor para essas duas tabelas. Por outro lado, se isso não for uma parte crítica do seu aplicativo, você poderá lidar com o índice "forçado".
EDITAR
Como está bem claro que o problema está no design, sugiro desenhar as relações como muitos para muitos. Se você tiver a chance de verificá-lo em seu ambiente de teste, aqui está o que eu faria:
1) Crie uma tabela temporária apenas para armazenar nome e id do produto:
create temporary table tmp_prods
select min(id) id, name
from ycs_products
group by name;
2) A partir da tabela temporária, junte-se à tabela de vendas para criar um substituto para o
ycs_product
:create table ycs_products_new
select * from tmp_prods;
ALTER TABLE `poc`.`ycs_products_new`
CHANGE COLUMN `id` `id` INT(11) NOT NULL ,
ADD PRIMARY KEY (`id`);
3) Crie a tabela de junção:
CREATE TABLE `prod_sale` (
`prod_id` INT(11) NOT NULL,
`sale_id` INT(11) NOT NULL,
PRIMARY KEY (`prod_id`, `sale_id`),
INDEX `sale_fk_idx` (`sale_id` ASC),
CONSTRAINT `prod_fk`
FOREIGN KEY (`prod_id`)
REFERENCES ycs_products_new (`id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION,
CONSTRAINT `sale_fk`
FOREIGN KEY (`sale_id`)
REFERENCES ycs_sales (`id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION);
e preenchê-lo com os valores existentes:
insert into prod_sale (prod_id, sale_id)
select tmp_prods.id, sales_id from ycs_sales s
inner join ycs_products p
on p.sales_id=s.id
inner join tmp_prods on tmp_prods.name=p.name;
Por fim, a consulta de junção:
select name, count(name) from ycs_products_new p
inner join prod_sale ps on ps.prod_id=p.id
inner join ycs_sales s on s.id=ps.sale_id
WHERE s.dtm BETWEEN '2018-02-16 00:00:00' AND '2018-02-22 23:59:59'
group by p.id;
Observe que o grupo por está na chave primária, não no nome.
Explique a saída:
explain select name, count(name) from ycs_products_new p inner join prod_sale ps on ps.prod_id=p.id inner join ycs_sales s on s.id=ps.sale_id WHERE s.dtm BETWEEN '2018-02-16 00:00:00' AND '2018-02-22 23:59:59' group by p.id;
+------+-------------+-------+--------+---------------------+---------+---------+-----------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+-------+--------+---------------------+---------+---------+-----------------+------+-------------+
| 1 | SIMPLE | p | index | PRIMARY | PRIMARY | 4 | NULL | 3 | |
| 1 | SIMPLE | ps | ref | PRIMARY,sale_fk_idx | PRIMARY | 4 | test.p.id | 1 | Using index |
| 1 | SIMPLE | s | eq_ref | PRIMARY,dtm | PRIMARY | 4 | test.ps.sale_id | 1 | Using where |
+------+-------------+-------+--------+---------------------+---------+---------+-----------------+------+-------------+