2017-07-13 4 views
0

Je suis coincé avec un problème de performance:Optimize requête SQL avec plusieurs jointures internes sur une même table

Un magasin a un filtre à l'article avec des catégories « couleur », « taille », « genre » et « fonction ». Tous ces détails sont stockés dans une table article_criterias, qui ressemble à ceci:

La disposition de table de article_criterias est; ce tableau a environ 36.000 lignes:

article_id | group | option | option_val 
     100 | "size" | "35" |  35.00 
     100 | "size" | "36" |  36.00 
     100 | "size" | "36½" |  36.50 
     100 | "color" | "40" |  40.00 
     100 | "color" | "50" |  50.00 
     100 | "gender" | "1" |  1.00 
     101 | "size" | "40" |  40.00 
     ... 

Nous avons une requête SQL qui est construit dynamiquement, en fonction des critères qui sont actuellement sélectionnés. La requête est bonne pour 2-3 critères, mais sera très lente lors de la sélection de plus de 5 options (chaque INNER JOIN supplémentaire double le temps d'exécution)

Comment pouvons-nous rendre ce SQL plus rapide, peut-être même remplacer les jointures internes avec un concept plus performant?

Ceci est la requête (la logique est correcte, juste la performance est mauvaise):

-- This SQL is generated when the user selected the following criteria 
-- gender: 1 
-- color: 80 + 30 
-- size 36 + 37 + 38 + 39 + 42 + 46 
SELECT 
    criteria.group AS `key`, 
    criteria.option AS `value` 
FROM articles 
    INNER JOIN article_criterias AS criteria ON articles.id = criteria.article_id 
    INNER JOIN article_criterias AS criteria_gender 
     ON criteria_gender.article_id = articles.id AND criteria_gender.group = "gender" 
    INNER JOIN article_criterias AS criteria_color1 
     ON criteria_color1.article_id = articles.id AND criteria_color1.group = "color" 
    INNER JOIN article_criterias AS criteria_size2 
     ON criteria_size2.article_id = articles.id AND criteria_size2.group = "size" 
    INNER JOIN article_criterias AS criteria_size3 
     ON criteria_size3.article_id = articles.id AND criteria_size3.group = "size" 
    INNER JOIN article_criterias AS criteria_size4 
     ON criteria_size4.article_id = articles.id AND criteria_size4.group = "size" 
    INNER JOIN article_criterias AS criteria_size5 
     ON criteria_size5.article_id = articles.id AND criteria_size5.group = "size" 
    INNER JOIN article_criterias AS criteria_size6 
     ON criteria_size6.article_id = articles.id AND criteria_size6.group = "size" 
    INNER JOIN article_criterias AS criteria_size7 
     ON criteria_size7.article_id = articles.id AND criteria_size7.group = "size" 
WHERE 
    AND (criteria_gender.option IN ("1")) 
    AND (criteria_color1.option IN ("80", "30")) 
    AND (criteria_size2.option_val BETWEEN 35.500000 AND 36.500000) 
    AND (criteria_size3.option_val BETWEEN 36.500000 AND 37.500000) 
    AND (criteria_size4.option_val BETWEEN 37.500000 AND 38.500000) 
    AND (criteria_size5.option_val BETWEEN 38.500000 AND 39.500000) 
    AND (criteria_size6.option_val BETWEEN 41.500000 AND 42.500000) 
    AND (criteria_size7.option_val BETWEEN 45.500000 AND 46.500000) 
+1

vérifier l'indexation, les champs utilisés dans INNER JOIN état, et où l'état. L'indexation accélérera la recherche. –

+4

Le problème auquel vous êtes confronté est "Valeur d'attribut d'entité" ou "EAV". Vous avez l'anti-pattern de l'enfer ici en ce qui concerne les bases de données relationnelles. Vous avez besoin d'une base de données différente ou d'une approche différente. – LoztInSpace

+1

Même si je suis d'accord avec l'astronaute, êtes-vous sûr que la requête fonctionne vraiment comme prévu?Votre requête va actuellement trouver des chaussures pour un sexe spécifique qui existe en couleurs 80 OR (!) 30 et existe en tailles 36 ET (!) 37 (et le reste), donc pas de résultat si vous l'avez dans toutes les tailles sauf 39. Vous pourrait déjà économiser beaucoup de jointures/temps ici si vous utilisez 'ou' pour les tailles aussi, semblable aux couleurs: seulement joindre une fois, par exemple en utilisant 'join ... criteria_size2 sur ... criteria_size2.group =" size "et (criteria_size2.option_val entre 35.5 et 36.5 ou criteria_size2.option_val entre 36.5 et 37.5 ou ...)'. – Solarflare

Répondre

2

tables clé/valeur sont vraiment une nuisance Cependant, afin de trouver certa. des critères matchs agrègent vos données:

select 
    a.*, 
    ac.group AS "key", 
    ac.option AS "value" 
from articles a 
join article_criterias ac on ac.article_id = a.article_id 
where a.article_id in 
(
    select article_id 
    from article_criterias 
    group by article_id 
    having sum("group" = 'gender' and option = '1') > 0 
    and sum("group" = 'color' and option in ('30','80')) > 0 
    and sum("group" = 'size' and option_val between 35.5 and 36.5) > 0 
    and sum("group" = 'size' and option_val between 36.5 and 37.5) > 0 
    and sum("group" = 'size' and option_val between 37.5 and 38.5) > 0 
    and sum("group" = 'size' and option_val between 38.5 and 39.5) > 0 
    and sum("group" = 'size' and option_val between 41.5 and 42.5) > 0 
    and sum("group" = 'size' and option_val between 45.5 and 46.5) > 0 
) 
order by a.article_id, ac.group, ac.option; 

Cela vous obtient tous les articles qui sont disponibles pour le sexe 1, les couleurs 30 et/ou 80, et toutes les gammes de taille énumérées, ainsi que toutes leurs options. (Les fourchettes de taille sont un peu étranges, cependant, une taille 36.5 rencontrerait deux gammes par exemple.) Vous obtenez l'idée: grouper par id_article et utiliser HAVING afin de n'obtenir que les articles_id qui répondent aux critères.

Quant aux indices que vous voulez

create index idx on article_criterias(article_id, "group", option, option_val); 
0

Comme suggéré par @ Affan-Pathan ajoutant indice a résolu le problème:

CREATE INDEX text_option 
ON `article_criterias` (`article_id`, `group`, `option`); 

CREATE INDEX numeric_option 
ON `article_criterias` (`article_id`, `group`, `option_val`); 

Les deux index coupent le temps d'exécution de la forme de requête ci-dessus près de 1 minute à moins de 50 millisecondes !!

0

Je comprends que les index que vous avez créés ont résolu votre problème, mais juste pour jouer avec une pseudo alternative (qui évite plusieurs INNER JOIN), pouvez-vous essayer quelque chose comme ça? (Je l'ai testé avec trois conditions: votre condition doit être insérée dans la requête interne Pour sélectionner uniquement l'enregistrement qui répond à toutes les conditions, vous devez changer la dernière condition WHERE (WHERE max = 3, en utilisant le nombre de conditions que vous avez écrit ci-dessus; donc si vous utilisez 5 conditions, vous devriez écrire WHERE max = 5). (J'ai changé le nom des groupes de colonnes et l'option, pour ma facilité d'utilisation.) C'est juste une idée si pls faire quelques tests et vérifier la performance et merci de me le faire savoir ...

CREATE TABLE CRITERIA (ARTICLE_ID INT, GROU VARCHAR(10), OPT VARCHAR(20), OPTION_VAL NUMERIC(12,2)); 
CREATE TABLE ARTICLES (ID INT); 
INSERT INTO CRITERIA VALUES (100,'size','35',35); 
INSERT INTO CRITERIA VALUES (100,'size','36',36); 
INSERT INTO CRITERIA VALUES (100,'color','40',40); 
INSERT INTO CRITERIA VALUES (100,'gender','1',1); 
INSERT INTO CRITERIA VALUES (200,'size','36.2',36.2); 
INSERT INTO CRITERIA VALUES (300,'size','36.2',36.2); 
INSERT INTO ARTICLES VALUES (100); 
INSERT INTO ARTICLES VALUES (200); 
INSERT INTO ARTICLES VALUES (300); 

------------------------------------------------------- 

SELECT D.article_id, D.GROU, D.OPT 
FROM (SELECT C.* 
    , @o:=CASE WHEN @h=ARTICLE_ID THEN @o ELSE cumul END max 
    , @h:=ARTICLE_ID AS a_id 
    FROM (SELECT article_id, 
      B.GROU, B.OPT,    
      @r:= CASE WHEN @g = B.ARTICLE_ID THEN @r+1 ELSE 1 END cumul,       
      @g:= B.ARTICLE_ID g     
      FROM CRITERIA B 
      CROSS JOIN (SELECT @g:=0, @r:=0) T1 
      WHERE (B.GROU='gender' AND B.OPT IN ('1')) 
        OR (B.GROU='color' AND B.OPT IN ('40', '30')) 
        OR (B.GROU='size' AND B.OPT BETWEEN 35.500000 AND 36.500000) 
      ORDER BY article_id 
    ) C 
CROSS JOIN (SELECT @o:=0, @h:=0) T2 
ORDER BY ARTICLE_ID, CUMUL DESC) D 
WHERE max=3 
; 

sortie:

article_id GROU OPT 
100 gender 1 
100 color 40 
100 size 36