sql >> Base de Datos >  >> RDS >> Mysql

Optimice una consulta que agrupe los resultados por un campo de la tabla unida

El problema es que agrupar por name te hace perder el sales_id información, por lo que MySQL se ve obligado a utilizar una tabla temporal.

Aunque no es la más limpia de las soluciones, y uno de mis enfoques menos favoritos, podría agregar un nuevo índice, en ambos el name y el sales_id columnas, como:

ALTER TABLE `yourdb`.`ycs_products` 
ADD INDEX `name_sales_id_idx` (`name` ASC, `sales_id` ASC);

y fuerza la consulta para usar este índice, ya sea con force index o 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;

Mi ejecución informó solo "usando where; usando índice" en la tabla p y "usando where" en la tabla s.

De todos modos, le sugiero encarecidamente que vuelva a pensar en su esquema, porque probablemente podría encontrar un mejor diseño para estas dos tablas. Por otro lado, si esto no es una parte crítica de su aplicación, puede lidiar con el índice "forzado".

EDITAR

Dado que está bastante claro que el problema está en el diseño, sugiero dibujar las relaciones como de muchos a muchos. Si tiene la oportunidad de verificarlo en su entorno de prueba, esto es lo que haría:

1) Cree una tabla temporal solo para almacenar el nombre y la identificación del producto:

create temporary table tmp_prods
select min(id) id, name
from ycs_products
group by name;

2) A partir de la tabla temporal, únase a la tabla de ventas para crear un reemplazo para el 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) Cree la tabla de unión:

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);

y rellénelo con los 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;

Finalmente, la consulta de unión:

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;

Tenga en cuenta que el grupo por está en la clave principal, no en el nombre.

Explique el resultado:

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