Mysql
 sql >> Base de Dados >  >> RDS >> Mysql

Otimizar uma consulta que agrupa os resultados por um campo da tabela unida


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 |
+------+-------------+-------+--------+---------------------+---------+---------+-----------------+------+-------------+