Mysql
 sql >> Database >  >> RDS >> Mysql

Ottimizza una query che raggruppa i risultati in base a un campo della tabella unita

Il problema è che il raggruppamento per name ti fa perdere il sales_id informazioni, pertanto MySQL è costretto a utilizzare una tabella temporanea.

Sebbene non sia la soluzione più pulita e uno dei miei approcci meno preferiti, potresti aggiungere un nuovo indice, su entrambi il name e il sales_id colonne, come:

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

e forza la query per utilizzare questo indice, 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;

La mia esecuzione riportava solo "usando dove; usando indice" sulla tabella pe "usando dove" sulla tabella s.

Ad ogni modo, ti consiglio vivamente di ripensare al tuo schema, perché probabilmente potresti trovare un design migliore per queste due tabelle. D'altra parte, se questa non è una parte critica della tua applicazione, puoi gestire l'indice "forzato".

MODIFICA

Poiché è abbastanza chiaro che il problema è nel design, suggerisco di disegnare le relazioni come molti-a-molti. Se hai la possibilità di verificarlo nel tuo ambiente di test, ecco cosa farei:

1) Crea una tabella temporanea solo per memorizzare nome e ID del prodotto:

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

2) Partendo dalla tabella temporanea, unisciti alla tabella vendite per creare un sostituto per il 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) Crea la tabella di unione:

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 riempilo con i valori esistenti:

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;

Infine, la query di unione:

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;

Tieni presente che il gruppo per è sulla chiave primaria, non sul nome.

Spiega l'output:

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