[SQL] Optimisation de requête "regroupement X-en-1" (tri ?)

Optimisation de requête "regroupement X-en-1" (tri ?) [SQL] - SQL/NoSQL - Programmation

Marsh Posté le 28-09-2007 à 12:37:50    

(edit: je mange un brin et je vous sors les specs du PC et des tables, au cas où ça ne soit *que* une question de puissance)
 
Hello ! J'ai besoin de votre aide pour optimiser la requête suivante :

SELECT TOP 1 catg.catgid catgid, catg.sfacatg sfacatg, mara.matnr matnr, mara.source source  
FROM smocatg catg, smocatgt catgt, smoprcat prcat, smocatrel catrel, smomakt makt, smomara mara, smomvke mvke  
WHERE catg.sfacatg = prcat.sfacatg AND catg.sfacatg *= catgt.sfacatg and catg.sfacatg = catrel.sfacatg
AND mara.sfamara *= makt.sfamara AND mara.sfamara = catrel.sfamara AND mara.sfamara = mvke.sfamara  
AND catrel.deleted = '0' AND makt.deleted = '0' AND mara.deleted = '0' AND mvke.deleted = '0'  
AND mvke.vmsta <> 'I1050' AND mara.lvorm = ' ' AND mara.prodstat <> 'I1103' AND mara.source = 'PAS'  
AND catrel.mandt = '000' AND makt.mandt = '000' AND mara.mandt = '000' AND makt.spras = 'EN'  
 
AND catg.catgid like 'BRA%'  
 
AND catg.sfahier = 'A9D4B1DDDAAE4848BBA5E2BFD13EE6A8'  
AND mvke.mvgr5 IN ('TR', 'PL') AND mvke.vkorg = 'O 50000810' AND mvke.vtweg = 'C1'  
GROUP BY catg.catgid, catg.sfacatg, mara.matnr, mara.source  
HAVING len(catg.catgid) = 9  
ORDER BY catg.catgid, catg.sfacatg, mara.matnr, mara.source


 
Le fonctionnement est le suivant :
C'est pour remplir un arbre hiérarchique
On a des produits classés en catégories avec plusieurs niveaux de hiérarchie : BRA, BRAP1, BRAP1B1, BRAP1B101.
Un produit est défini avec sa hiérarchie complète, à 9 caractères (clause HAVING)
A la branche de plus haut niveau (hiérarchie BRA) je veux attacher le premier produit trouvé (SELECT TOP 1), et il y a 12 branches.
 
Donc le truc est que cette requête va être jouée 12 fois, avec pour seule variation le paramètre catg.catgid.
Problème : 1 requête prend environ 3s : les 12 requêtes (donc l'affichage) prennent entre 35s et 40s.
Solution : faire 1 requête qui récupère mes le premier produit de chacune des 12 catégories, et ensuite je parcours via le code.
 
J'imagine que fait une union des 12 requêtes me prendrait le même temps
Ce qu'il me faudrait, c'est pouvoir trier selon mes 12 critères, mais comme il y a plus qu'1 produit par catégorie pour l'instant je récupère un truc du genre
BRAP1B101 / Produit 1
BRAP1B101 / Produit 2
BRAP1B102 / Produit 1
BRAP1B102 / Produit 2
BRAP1B201 / Produit 1
BRAP2B101 / Produit 1
ECLF1Z322 / Produit 1
 
Alors que je voudrais seulement les catégories uniques :  
BRAxxxxxx / Produit 1
ECLxxxxxx / Produit 1
 
Evidemment, pour être un peu chiant, quand on descendra la hiérarchie mon catg.catgid prendra la valeur 'BRAP1%', par exemple, donc il me faudra chaque produit des X catégories BRAP1xxxx, BRAP2xxxx, BRAP3xxxx, etc...
 
Une idée, quelqu'un ?
Merci :)


Message édité par yyyeeeaaahhh le 28-09-2007 à 14:24:33
Reply

Marsh Posté le 28-09-2007 à 12:37:50   

Reply

Marsh Posté le 28-09-2007 à 13:35:02    

explain analyse

Reply

Marsh Posté le 28-09-2007 à 14:03:55    

je ne connais pas explain analyse, mais en cherchant j'ai essayé  
explain analyse SELECT [.....]
Could not find stored procedure "explain"
 
Et explain ou analyse seul donne la même chose, il cherche une procédure stockée
 
J'utilise le SQL Query Analyser dans le package Microsoft SQL Server
Je vais tenter de chopper un TOAD ou qqch de rapide
 
En attendant :
Machines :  
   Test = Pentium 4-M 1.8GHz, 512Mo de RAM sous Windows 2000
   Utilisateurs finaux (de mémoire) : Pentium M 1.5GHz, 1Go RAM, Windows 2000 ou XP
Population des tables :

SMOMVKE   : 373000
SMOMAKT   : 294000
SMOCATREL : 168000
SMOMARA   :  42000
SMOCATGT  :  30000
SMOCATG   :   6500
SMOPRCAT  :   6500

Reply

Marsh Posté le 28-09-2007 à 16:35:26    

PUTAIN DE SOURIS DE MERDE
 
Molette partie en vrille pendant que je faisais CTRL au clavier, je viens de perdre mon post. Bordel de Dieu ça me gonfle :fou:
 
Donc, je reprends depuis le début... :o
 
1/ Passe en notation ANSI, c'est pas très clair là ;)
2/ Ton GROUP BY est inutile, car tu ne fais pas de fonction de regroupement. Cela implique une perte de temps très important durant la requête
3/ Ton HAVING est inutile. Met plutôt la condition dans ta clause WHERE, ce sera beaucoup plus rapide
4/ Le ORDER BY est redondant avec ton GROUP BY (mêmes champs dans le même ordre, alors que le GROUP BY fait déjà un tri). Mais vu que tu vas virer le GROUP BY, conserve le ORDER BY ;)
 
Sinon, pas tout pigé à ton problème. Voici donc une simple réécriture de ta requête en suivant les 4 suggestions que je t'ai fait, mais qui donnera le même résultat, donc pas de solution au problème :
 

Code :
  1. SELECT TOP 1
  2.    catg.catgid,
  3.    catg.sfacatg sfacatg,
  4.    mara.matnr matnr,
  5.    mara.source source  
  6. FROM smocatg catg
  7. INNER JOIN smoprcat prcat ON prcat.sfacatg = catg.sfacatg
  8. INNER JOIN smocatrel catrel ON catrel.sfacatg = catg.sfacatg
  9. INNER JOIN smomara mara ON mara.sfamara = catrel.sfamara
  10. INNER JOIN smomvke mvke ON mvke.sfamara = mara.sfamara
  11. LEFT OUTER JOIN smocatgt catgt ON catgt.sfacatg = catg.sfacatg
  12. LEFT OUTER JOIN smomakt makt ON makt.sfamara = mara.sfamara
  13. WHERE
  14.    catg.catgid LIKE 'BRA%' AND catg.sfahier = 'A9D4B1DDDAAE4848BBA5E2BFD13EE6A8' AND len(catg.catgid) = 9
  15. AND catrel.deleted = '0' AND catrel.mandt = '000'
  16. AND mara.deleted = '0' AND mara.lvorm = ' ' AND mara.prodstat <> 'I1103' AND mara.source = 'PAS' AND mara.mandt = '000'
  17. AND mvke.deleted = '0' AND mvke.vmsta <> 'I1050' AND mvke.mvgr5 IN ('TR', 'PL') AND mvke.vkorg = 'O 50000810' AND mvke.vtweg = 'C1'
  18. AND makt.deleted = '0' AND makt.mandt = '000' AND makt.spras = 'EN'  
  19. ORDER BY catg.catgid, catg.sfacatg, mara.matnr, mara.source


 
PAR CONTRE : Ma requête ne répond pas à ta question (au mieux, elle optimise un peu).
 
Effectivement, dans ta requête, tu retournes 4 champs. Mais dans ton explication, tu ne parles que de 2 champs.
 
A quoi correspondent chacuns de tes 4 champs ?
 
En fait, de ce que je comprends, pour chaque "X premiers caractères de catgid" tu veux "le premier produit".
 
Pour se faire, il faut donc simplement faire une sous-jointure qui récupère l'identifiant de ce "premier produit" pour chaque "X premiers caractères de catgid", puis simplement faire des jointures sur cette sous-requête afin de récupérer les 2 autres champs à partir de ces codes produits retournés.

Message cité 1 fois
Message édité par MagicBuzz le 28-09-2007 à 16:36:39
Reply

Marsh Posté le 28-09-2007 à 17:03:20    

Si tu veux un regroupement 3 en 1, il te faudra du Ajax.

 
Spoiler :

Oui, j'ai honte.


Message édité par WiiDS le 28-09-2007 à 17:03:29

---------------
"I can cry like Roger. It's just a shame I can't play like him" - Andy Murray, 2010
Reply

Marsh Posté le 28-09-2007 à 18:00:05    

MagicBuzz a écrit :

PUTAIN DE SOURIS DE MERDE


(je ne quote que ça, j'étais mort de rire et parce que ça m'est arrivé une paire de fois ; un peu comme les timeout quand on envoie le mail sans avoir copié avant...)
 
Bref, ton 1/ notation ANSi, c'est la balise [ code=sql] ? Dans ce cas merci, je connais pas vraiment les possibilités offertes par le forum :)
 
Ensuite, utiliser les jointures plutôt que des clauses WHERE c'est plus performant, ou juste plus pratique ? Question d'habitude peut-être, ça fait longtemps que j'avais pas eu à me taper des requêtes
 
Les autres champs servent pour le traitement, je développe du spécifique en plein milieu de standard donc je me débrouille pour qu'il récupère ce qu'il veut  
 - catgid ne me sert à priori à rien :(, je me suis persuadé que je devais le sélectionner pour pouvoir faire mon GROUP BY puis ORDER BY (à ce propos dans certains cas le GROUP BY ne trie pas de la même façon (perdu le lien), peut-être en cas d'UNION)
 - sfacatg ne sert à hmmm rien :( (je pensais m'en servir pour les catégories parent mais j'ai abandonné l'idée)
 - matnr est mon tag de noeud hiérarchique, que je récupère par la suite
 - source sert dans une condition après, c'est vrai que je le force dans le WHERE donc je vais sélectionner 'PAS' As source pour éviter de traîner un autre champ
 
Merci pour cette optimisation !!  :jap:  
j'intègre ça de suite en attendant une éventuelle solution à ma question de départ.
 
En fait c'est 18h, je me casse en vacances pour 2 semaines, on verra après :p
 
<<<<<EDIT>>>>>
Le pourquoi du GROUP BY et des 4 champs : je viens de tester ta solution, et mettre la clause sur la taille dans le WHERE prend minimum 2x plus de temps (testé avec plusieurs valeurs pour éviter les problèmes de mise en cache de la requête) que le HAVING, et HAVING nécessite un GROUP BY.
Ensuite, je suis obligé de mettre chaque champ du SELECT ou du ORDER BY dans le GROUP, sinon il ne veut pas l'exécuter
Voilà, j'ai retrouvé ma raison :D
Du coup avec les jointures je ne note pas d'amélioration sensible.
Merci quand même d'avoir essayé !  :jap:  
18h25, je veux mes vacances !!!§1§oneelevenShift+1omgbrbbbq (désolé  :hello: )


Message édité par yyyeeeaaahhh le 28-09-2007 à 18:25:12
Reply

Marsh Posté le 28-09-2007 à 20:32:25    

La notation ANSI, c'est pas la balise (même si c'est plus clair) mais les jointures explicites dans le FROM.
Cela a plusieurs avantages :
- Bien plus lisible pour une personne qui reli la requête sans connaître le modèle
- Parfois énormément plus performant. En tout logique, point de vue performances, ça ne devrait rien changer, mais parfois l'optimiseur se plante et ramme pour pas grand chose. A noter toutefois que si tu indiques des critères foireux dans la notation ANSI, ça peut être plus lent que sans (en fait, tu maîtrises la façon dont le moteur SQL va exécuter la requête, avec tous les avantages et inconvénients que cela représente)
- Sépare proprement les critères de jointure et les critères de filtre, cela aide à la compréhension globale de la requête
- Permet de faire des LEFT et RIGHT OUTER JOIN en cascade, ce qui est impossible avec la notation non ANSI

 

Sinon, je suis très étonné pour le coup du GROUP BY + WHERE plus rapide que sans. En toute logique ça aurait dû être plus rapide. J'en déduit que mon FROM est mal écrit (vu que je pige pas trop ton modèle, c'est bien possible) ou un problème d'index.

 

Effectivement, pour simplifier :

 

FROM + WHERE : sont exécutés au moment de la requête elle-même. Toutes les données qui ne répondent pas aux critères de ces deux clauses sont directement ignorés par le moteur SQL. Ainsi, les données ne sont pas chargées inutiliement en mémoire, ni lues inutilement sur le disque. Il en résulte une meilleure montée en charge (moins d'occupation mémoire) et une plus grande rapidité (moins de lectures sur le disque).
Le GROUP BY et le HAVING sont au contraire des instructions "post-processing". Ainsi, elle vont porter sur le résultat de la requête entière, et filtrer/regrouper les lignes retournées. Ainsi, tu manipules en mémoire des données inutilement.

 

Du coup, je suis tenté de dire que contrairement à ce que je pensais, catgid n'est pas le point d'entrée de la requête, et que certainement tu as des index non performants pour cette requête : ainsi, le filtrage des données dans le WHERE ne portent pas sur un index, mais sur les données du disque, ce qui oblige à les lire de toute façon.

 

Sinon, ok, matnr c'est ton "BRAP..." mais "Produit 1", ça correspond à quel champ ?

 

(PS : C'est toujours MagicBuzz)


Message édité par Arjuna le 28-09-2007 à 20:33:21
Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed