Mysql
 sql >> Base de données >  >> RDS >> Mysql

Optimiser une requête qui regroupe les résultats par un champ de la table jointe

Le problème est que le regroupement par name vous fait perdre le sales_id informations, MySQL est donc obligé d'utiliser une table temporaire.

Bien que ce ne soit pas la plus propre des solutions, et l'une de mes approches les moins préférées, vous pouvez ajouter un nouvel index, sur les deux le name et le sales_id colonnes, comme :

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

et forcer la requête pour utiliser cet index, avec soit 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;

Mon exécution n'a signalé que "using where; using index" sur la table p et "using where" sur la table s.

Quoi qu'il en soit, je vous suggère fortement de repenser votre schéma, car vous pourriez probablement trouver une meilleure conception pour ces deux tables. En revanche, s'il ne s'agit pas d'une partie critique de votre application, vous pouvez vous occuper de l'index "forcé".

MODIFIER

Puisqu'il est tout à fait clair que le problème réside dans la conception, je suggère de dessiner les relations sous la forme d'un plusieurs à plusieurs. Si vous avez la possibilité de le vérifier dans votre environnement de test, voici ce que je ferais :

1) Créez une table temporaire juste pour stocker le nom et l'identifiant du produit :

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

2) À partir de la table temporaire, rejoignez la table des ventes pour créer un remplacement pour le 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) Créez la table de jointure :

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

et remplissez-le avec les valeurs existantes :

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;

Enfin, la requête de jointure :

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;

Veuillez noter que le groupe par est sur la clé primaire, pas sur le nom.

Expliquez la sortie :

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