[SQL] Jointure avec un OR [Résolu]

Jointure avec un OR [Résolu] [SQL] - SQL/NoSQL - Programmation

Marsh Posté le 06-05-2009 à 15:42:36    

Bonjour,
 
J'ai un pb plutot complexe sur une requete, si qq a une solution :  
Pour faire simple, 2 tables :  
GROUPS
- grp_id (int) primary
- grp_name (varchar)
 
et
MEETING
- met_id (int) primary
- grp1 (int)
- grp2 (int)
- datemeeting (date)
- met_status (int) index
+ des index sur grp1, grp2, et grp1+grp2
 
Bref en gros, c'est une structure qui permet de loguer des rdv entre 2 groupes. Dans la table meeting, chaque grp1 et grp2 sont des clé etrangere vers grp_id de la table groups.
Et du coup, j'ai des requetes qui sont de la sorte :  
 

Code :
  1. SELECT g.grp_id, g.grp_name, m.datemeeting
  2. FROM groups g
  3. INNER JOIN meeting m ON (g.grp_id = m.grp1 OR g.grp_id = m.grp2) AND m.met_status = 3


 
Le probleme est que c'est pas tres optimisé du coup, et ca rame parfois (la table meeting fait dans les 20000 reg, la table groups dans les 5000)
 
Je voulais savoir comment optimiser/modifier ma jointure avec un OR
 
Merci d'avance
 
PS : Evidemment, les vrais structures sont plus complexe, mais je pense que pour l'exemple c'est suffisant
 
Edit: y'aurais bien la solution de faire un UNION avec 2 fois presque la meme requete, mais je suis pas sur que ca soit vraiment mieux, ni tres propre meme...

Message cité 2 fois
Message édité par jmbianca le 06-05-2009 à 17:50:57
Reply

Marsh Posté le 06-05-2009 à 15:42:36   

Reply

Marsh Posté le 06-05-2009 à 16:30:20    

La requête est obligée de lire tous les enregistrements de la MEETING pour trouver le grp1 ou le grp2 qui puisse être relié à un groupe. Il n'est pas étonnant que cela prenne beaucoup de temps. Changer la requête ne vas pas donner d'amélioration si la jointure reste sur ces champs-là.
 
La solution consite à créer un index secondaire sur meeting.grp1 et d'un autre index secondaire sur meeting.grp2. La création d'un index est facile à faire. Au besoin, demander à un DBA de le faire. L'accelération sera impressionnante.


Message édité par olivthill le 06-05-2009 à 16:31:03
Reply

Marsh Posté le 06-05-2009 à 16:39:16    

Merci pour ta réponse, mais comme je l'ai mis dans la description de la table Meeting, il y a deja des index secondaires :  
- sur grp1
- sur grp2
- sur grp1 + grp2
Mais la requete met presque 20 secondes a s'executer.
Pour info, c'est sur mySQL, des fois qu'il y ai des optimisations propre a ce SGBD qui soit applicable

Reply

Marsh Posté le 06-05-2009 à 16:40:01    

jmbianca a écrit :


Code :
  1. SELECT g.grp_id, g.grp_name, m.datemeeting
  2. FROM groups g
  3. INNER JOIN meeting m ON (g.grp_id = m.grp1 OR g.grp_id = m.grp2) AND m.met_status = 3



 
Je vais a priori dire une connerie, mais quel est l'intérêt de mettre les deux groupes d'un même meeting dans 2 lignes de résultat différentes? :??:


---------------
Can't buy what I want because it's free -
Reply

Marsh Posté le 06-05-2009 à 16:43:01    

La question derrière c'est : est-ce que tu ne pourrais pas tout simplement sélectionner tout le contenu de ta table meeting avec met_status=3 et faire le traitement dans le langage qui récupère le résultat derrière?


---------------
Can't buy what I want because it's free -
Reply

Marsh Posté le 06-05-2009 à 16:45:20    

c'est pas une connerie, mais disons que pour les requetes reellement utilisés sur le site, ca a un sens. En gros, ca permet de sortir tous les groupes present a un evenement, qu'ils y ait participé en tant que grp1 ou grp2 (grp1 et grp2 n'ayant pas exactement la meme signification sur le site)

Reply

Marsh Posté le 06-05-2009 à 16:45:34    

D'ailleurs c'est voulu le met_status=3 dans la jointure au lieu d'avoir une clause where?


---------------
Can't buy what I want because it's free -
Reply

Marsh Posté le 06-05-2009 à 16:47:26    

@skeye : non parce que typiquement, j'utilise ce genre de requete pour faire des regroupements sur les autres champs. Et c'est quand meme plus rapide en SQL qu'en PHP

Reply

Marsh Posté le 06-05-2009 à 16:49:35    

@skeye: le met_status=3 dans la jointure au lieu d'avoir une clause where est volontaire ouais. Ca permet de mettre toutes les clauses qui concernent une table au meme endroit. C'est plus propre et ca permet de mettre rapidement en commentaire une table quand on veux faire des tests. Du coup je me sert du WHERE que pour la table principale

Reply

Marsh Posté le 06-05-2009 à 16:50:05    

jmbianca a écrit :

ca permet de sortir tous les groupes present a un evenement, qu'ils y ait participé en tant que grp1 ou grp2 (grp1 et grp2 n'ayant pas exactement la meme signification sur le site)


Justement, ta requête ne fait pas la différence...et tu répètes la date systématiquement...bref.
Par curiosité, niveau timing ça donne quoi comme différence ta requête par rapport à
 
SELECT m.*, g1.grp_name, g2.grp_name
FROM meeting
        JOIN group g1 ON (g1.grp_id = m.grp1)
        JOIN group g2 ON (g2.grp_id = m.grp2)  
WHERE m.met_status = 3
 
:??:


---------------
Can't buy what I want because it's free -
Reply

Marsh Posté le 06-05-2009 à 16:50:05   

Reply

Marsh Posté le 06-05-2009 à 16:52:10    

(sachant que récupérer le résultat de ma requête pour en faire la même chose en php qu'avec la tienne est trivial...)


Message édité par skeye le 06-05-2009 à 16:52:19

---------------
Can't buy what I want because it's free -
Reply

Marsh Posté le 06-05-2009 à 16:55:49    

jmbianca a écrit :


Je voulais savoir comment optimiser/modifier ma jointure avec un OR


une auto-jointure ?
[:grilled]


Message édité par Harkonnen le 06-05-2009 à 16:56:15

---------------
J'ai un string dans l'array (Paris Hilton)
Reply

Marsh Posté le 06-05-2009 à 16:56:50    

Je me rends compte que mon exemple n'etait pas suffisant. En fait, je fais des regroupement style :  
 

Code :
  1. SELECT g.grp_id, g.grp_name,
  2. SUM(CASE WHEN g.grp_id != m.sender_grp_id THEN 1 ELSE 0 END) AS nb_rdv_ask,
  3. SUM(CASE WHEN m.mas_status = 1 THEN 1 ELSE 0 END) AS nb_rdv_ok
  4. FROM groups g
  5. LEFT JOIN meeting m ON m.con_id = 8 AND (g.grp_id = m.grp1 OR g.grp_id = m.grp2)
  6. WHERE g.con_id = 8 AND g.grp_valid = 1
  7. GROUP BY g.grp_id, g.grp_name


 
Ce que je ne peux pas faire avec ta solution, qui va me renvoyer trop de resultats dans les SUM
PS : il y a des champs en plus qui n'etaient pas dans mon exemple simplifié, mais ca devrait pas changer grand chose

Reply

Marsh Posté le 06-05-2009 à 17:03:52    

jmbianca a écrit :

Je me rends compte que mon exemple n'etait pas suffisant. En fait, je fais des regroupement style :  
 

Code :
  1. SELECT g.grp_id, g.grp_name,
  2. SUM(CASE WHEN g.grp_id != m.sender_grp_id THEN 1 ELSE 0 END) AS nb_rdv_ask,
  3. SUM(CASE WHEN m.mas_status = 1 THEN 1 ELSE 0 END) AS nb_rdv_ok
  4. FROM groups g
  5. LEFT JOIN meeting m ON m.con_id = 8 AND (g.grp_id = m.grp1 OR g.grp_id = m.grp2)
  6. WHERE g.con_id = 8 AND g.grp_valid = 1
  7. GROUP BY g.grp_id, g.grp_name



 
Déjà ta jointure est pas super là, ça donnerait plutôt ça si je lis bien :
 

Code :
  1. SELECT g.grp_id, g.grp_name,
  2. SUM(CASE WHEN g.grp_id != m.sender_grp_id THEN 1 ELSE 0 END) AS nb_rdv_ask,
  3. SUM(CASE WHEN m.mas_status = 1 THEN 1 ELSE 0 END) AS nb_rdv_ok
  4. FROM groups g
  5. LEFT JOIN meeting m ON (m.con_id = g.con_id AND (g.grp_id = m.grp1 OR g.grp_id = m.grp2) )
  6. WHERE g.con_id = 8
  7. AND g.grp_valid = 1
  8. GROUP BY g.grp_id, g.grp_name


---------------
Can't buy what I want because it's free -
Reply

Marsh Posté le 06-05-2009 à 17:07:57    

certes, sauf que j'ai volontairement pas fait de jointure sur m.con_id = g.con_id pour lui laisser la possibilité d'utiliser les index sur grp1/grp2, ce qui m'interresse plus en terme de perfs (con_id seul renvoie trop de lignes)

Reply

Marsh Posté le 06-05-2009 à 17:08:57    

...bon puis là s'il s'agit de faire du group by là-dessus je me ferais pas chier et je ferais ça sur l'union des deux cas et basta...[:joce]


---------------
Can't buy what I want because it's free -
Reply

Marsh Posté le 06-05-2009 à 17:10:36    

jmbianca a écrit :

certes, sauf que j'ai volontairement pas fait de jointure sur m.con_id = g.con_id pour lui laisser la possibilité d'utiliser les index sur grp1/grp2, ce qui m'interresse plus en terme de perfs (con_id seul renvoie trop de lignes)


 
D'après le nom de ce champ c'est une clé étrangère, avoir un index dessus dans tes deux tables ne me parait pas délirant...et de toute manière je vois pas pourquoi il utiliserait pas les index sur grp1/2 dans ma version...


---------------
Can't buy what I want because it's free -
Reply

Marsh Posté le 06-05-2009 à 17:10:37    

@skeye : un peu HS, mais comment tu fais pour faire la coloration syntaxique sur le SQL ?

Reply

Marsh Posté le 06-05-2009 à 17:11:29    

jmbianca a écrit :

@skeye : un peu HS, mais comment tu fais pour faire la coloration syntaxique sur le SQL ?


 


[code=sql]
select ...
[/code]


---------------
Can't buy what I want because it's free -
Reply

Marsh Posté le 06-05-2009 à 17:11:56    

de toute facon soit tu fais un index composite, soit deux index, un sur grp1 et un sur grp2 et tu mets 2 left join du coup et tu rajoutes le m1.meeting_id = m2.meeting_id dans ton 2ème join.

Reply

Marsh Posté le 06-05-2009 à 17:13:19    

y'a bien un index sur con_id dans mes 2 tables, mais encore une fois, c'est pas assez interressant en terme de perf (d'ailleurs mon ex. fait que c'est cet index qu'il utilise, et qui me fait des requetes de 20 secondes).
Et il utilise pas les index sur grp1/grp2 justement parce que c'est un OR. C'est tout le sujet de mon post au départ

Reply

Marsh Posté le 06-05-2009 à 17:14:12    

(puis sinon on revient à la solution du calcul en php après coup, c'est quand même pas bien compliqué et ça doit pas être si lourd que ça de faire quelques centaines de 1+1+0+1+1+0.... :D)


Message édité par skeye le 06-05-2009 à 17:14:37

---------------
Can't buy what I want because it's free -
Reply

Marsh Posté le 06-05-2009 à 17:15:50    

jmbianca a écrit :

y'a bien un index sur con_id dans mes 2 tables, mais encore une fois, c'est pas assez interressant en terme de perf (d'ailleurs mon ex. fait que c'est cet index qu'il utilise, et qui me fait des requetes de 20 secondes).
Et il utilise pas les index sur grp1/grp2 justement parce que c'est un OR. C'est tout le sujet de mon post au départ


Bataille pas dans ce cas, soit tu sépares les deux cas et tu regroupes en php (pas bien compliqué), soit tu te bases sur ma première requête et tu fais les sommes en php (probablement plus lourd...à tester)


Message édité par skeye le 06-05-2009 à 17:16:01

---------------
Can't buy what I want because it's free -
Reply

Marsh Posté le 06-05-2009 à 17:17:46    

@casimir: si je fait 2 left join sur meeting et que je rajoute m1.met_id = m2.met_id, je vois pas l'interet... (ou alors j'ai loupé un truc)

Reply

Marsh Posté le 06-05-2009 à 17:21:01    

@skeye : ta 1ere solution revient a faire un UNION, mais bon, je vais voir si j'y gagne en perfs, ca vaudra le coup, meme si c'est pas super propre...

Reply

Marsh Posté le 06-05-2009 à 17:24:50    

jmbianca a écrit :

@skeye : ta 1ere solution revient a faire un UNION, mais bon, je vais voir si j'y gagne en perfs, ca vaudra le coup, meme si c'est pas super propre...


C'est ta structure de tables qui est pas propre, amha :D
Si tu avais une tablemeeting (met_id, datemeeting, met_status, sender_grp_id, mas_status, ...) et une autre meeting_group(grp_id, met_id) tu aurais pas tous ces problèmes. :D


Message édité par skeye le 06-05-2009 à 17:24:56

---------------
Can't buy what I want because it's free -
Reply

Marsh Posté le 06-05-2009 à 17:29:34    

ce qui serait interressant si les meetings se faisait avec un seul groupe... mais dans le cas présent il y a 2 groupes dans chaque meeting, et il n'ont pas tout a fait le meme role suivant qu'ils sont dans grp1 ou grp2 (ce qui peut changer d'un meeting a l'autre). Je pourrais egalement transposer cette info dans ta table meeting_group, mais ca ne ferait que deplacer le pb en rajoutant une table X 2 dans toutes les requetes...

Reply

Marsh Posté le 06-05-2009 à 17:31:09    

jmbianca a écrit :

ce qui serait interressant si les meetings se faisait avec un seul groupe


euh, non. :D
Le but du jeu c'est justement d'avoir plusieurs lignes dans cette table pour chaque meeting. Et cadeau bonux, tu peux même envisager d'ajouter un flag "role_groupe" dans cette table et c'est gagné...[:dawa]


---------------
Can't buy what I want because it's free -
Reply

Marsh Posté le 06-05-2009 à 17:34:57    

Ta requête précédente deviendrait un truc de ce style :
 

Code :
  1. SELECT g.grp_id, g.grp_name,
  2. SUM(CASE WHEN g.grp_id != m.sender_grp_id THEN 1 ELSE 0 END) AS nb_rdv_ask,
  3. SUM(CASE WHEN m.mas_status = 1 THEN 1 ELSE 0 END) AS nb_rdv_ok
  4. FROM groups g
  5.    LEFT JOIN meeting_group mg ON (mg.con_id = g.con_id AND mg.grp_id = g.grp_id)
  6.    LEFT JOIN meeting m ON (m.met_id = mg.met_id)
  7. WHERE g.con_id = 8
  8. AND g.grp_valid = 1
  9. GROUP BY g.grp_id, g.grp_name


 
...et là ça coule tout seul...


---------------
Can't buy what I want because it's free -
Reply

Marsh Posté le 06-05-2009 à 17:50:26    

Yep, mais je me vois pas changer les 10zaines de requetes du site qui vont taper dans meeting pour ca. Mon but au départ etait de trouver une "astuce" pour résoudre la jointure avec le OR.
Enfin bref, j'ai fait la modif avec le UNION, c'est bcp mieux, et je vais rester la dessus, meme si c'est pas le plus "propre".
Merci a tous pour le coup de main

Reply

Marsh Posté le 06-05-2009 à 18:18:04    

jmbianca a écrit :

@casimir: si je fait 2 left join sur meeting et que je rajoute m1.met_id = m2.met_id, je vois pas l'interet... (ou alors j'ai loupé un truc)


 
tu es obligé de joiner ton meeting 1 avec meeting 2 sinon tu va faire un cross entre tres meetings.
 
ca donnerait ca mais le post part un peu dans tous les sens sans trop tout tester avant de rejeter le solution alors j'ai un peu de mal.
 
pour les index composite tu as vérifié le plan d'exécution? et si tu remplaces le or par un in?
 

Code :
  1. SELECT g.grp_id, g.grp_name,
  2. SUM(CASE WHEN g.grp_id NOT IN (m.sender_grp_id,m2.sender_grp_id) THEN 1 ELSE 0 END) AS nb_rdv_ask,
  3. SUM(CASE WHEN m.mas_status = 1 THEN 1 ELSE 0 END) AS nb_rdv_ok
  4. FROM groups g
  5. LEFT JOIN meeting m ON (m.con_id = g.con_id AND g.grp_id = m.grp1 )
  6. LEFT JOIN meeting m2 ON (m.con_id = g.con_id AND g.grp_id = m.grp2 AND m.meet_id = m2.meet_id)
  7. WHERE g.con_id = 8
  8. AND g.grp_valid = 1
  9. GROUP BY g.grp_id, g.grp_name


Reply

Marsh Posté le 07-05-2009 à 08:55:36    

@casimimir : merci pour tes propositions. Je vais essayer de tester ca aujourd'hui, mais comme je passe la journée en reunion, ca va pas etre facile...

Reply

Marsh Posté le    

Reply

Sujets relatifs:

Leave a Replay

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