Requete conditionelle complexe (mysql)

Répondre


Cette question est un moyen d’empêcher des soumissions automatisées de formulaires par des robots.
Smileys
:D :) :( :o :shock: :? 8-) :lol: :x :P :oops: :cry: :evil: :twisted: :roll: :wink: :!: :?: :idea: :arrow: :| :mrgreen: =D> #-o =P~ :^o :non: :priere: 8-|
Voir plus de smileys
  Revue du sujet
 

  Étendre la vue Revue du sujet : Requete conditionelle complexe (mysql)

par p_m_g » 26 nov. 2007, 12:57

merci pour ces précieuses info.
j'aimerais tant pouvoir effectuer des requetes imbriquées, mais je suis en mysql 4.0.xx.
il faut faire avec...

y a t-il autre chose que je puisse modifier alors au niveau de la requete ?

autre chose, dans ma page je fais plusieurs autres requetes que je pourrais peut etre eviter.
je fais une 1ère requete pour retourner le nombre total de dossiers, ensuite je refais la meme requete en mettant un ordre d'affichage et une limite.
ensuite je fais 2 requetes (identique à 1ere) mais en changeant le type. ainsi si j'ai au moins 1 resultat, j'affiche un lien (avec le nombre de résultat) disant que l'utilisateur à des dossiers avec un autre type.
je dois peut etre pouvoir effectuer en 1 seule requete le nombre de dossiers en fonctions des 3 types pour 1 utilisateur ?

y a t-il un moyen de gérer l'affchage page à page (limit) et le retour du nombre total de dossiers dans la meme requete ?

voici la requete qui sert à gérer l'affichage des dossiers et des pages pour un utilisateur de type C :
SELECT l1.id_dossier, l1.ref, l5.nom_prenom AS nom_prenom_d1, l7.nom_prenom AS nom_prenom_d2, l8.nom_prenom AS nom_prenom_f
FROM dossiers AS l1
LEFT JOIN liaison_dossier AS l2 ON ( l1.id_dossier = l2.id_dossier AND l2.type_user = '".$type."' )
LEFT JOIN liaison_dossier AS l3 ON ( l1.id_dossier = l3.id_dossier AND l3.type_user = 'D' )
LEFT JOIN liaison_dossier AS l4 ON ( l1.id_dossier = l4.id_dossier AND l4.type_user = 'A' )
LEFT JOIN liaison_dossier AS l6 ON ( l1.id_dossier = l6.id_dossier AND l6.type_user = 'B' )
LEFT JOIN users AS l5 ON ( l4.id_user = l5.id_user )
LEFT JOIN users AS l7 ON ( l6.id_user = l7.id_user )
LEFT JOIN users AS l8 ON ( l3.id_user = l8.id_user )
WHERE ( l2.id_user = '".$id."' OR ( l3.id_user = '".$id."' AND l4.type_user IS NOT NULL AND l2.type_user IS NULL ) )
GROUP BY l1.id_dossier
ORDER BY ".$SELECTION." ".$ORDRE."
LIMIT ".$debut.",".$limit

par Hubert Roksor » 26 nov. 2007, 12:20

Salut,

Désolé de ne pas avoir réagi plus tôt, j'ai été pris par d'autres trucs.

Concernant ta requête, je pense que tu dois pouvoir la simplifier en la prenant sous un autre angle. Il existe sous MySQL 4.1+ une fonction, GROUP_CONCAT() qui permet d'obtenir plus facilement la liste des valeurs d'une colonne pour un groupe donné. Par exemple

Code : Tout sélectionner

SELECT id_dossier, GROUP_CONCAT(DISTINCT type_user ORDER BY type_user) AS types FROM liaison_dossier WHERE id_user = 'XXX' GROUP BY id_dossier
...te renverra quelque chose comme

Code : Tout sélectionner

123 A,B 345 A 456 B,D
À partir de là, tu peux soit utiliser un système de CASE-WHEN en SQL pour remplacer dynamiquement les groupes contenant un D, eg

Code : Tout sélectionner

SELECT id_dossier, CASE GROUP_CONCAT(DISTINCT type_user ORDER BY type_user) WHEN 'A,B,C' THEN 'A,B,C' WHEN 'A,B' THEN 'A,B' WHEN 'A,B,D' THEN 'A,B,C' WHEN 'B,C,D' THEN 'A,B,C' WHEN 'B,D' THEN 'A,B' WHEN 'A,B,C,D' THEN 'A,B,C' ELSE 'INVALIDE' END AS types FROM liaison_dossier WHERE id_user = 'XXX' GROUP BY id_dossier
Là dessus, tu peux encore filtrer les résultats grâce à une clause HAVING, si besoin était. Selon le filtre à appliquer ce n'est peut-être pas la façon la plus optimisée de faire, mais elle a l'avantage de rester relativement simple (donc moins de chance de se planter et s'en apercevoir 3 mois plus tard) et devrait être suffisamment performante pour un usage courant.

Ainsi, grâce à la fonction FIND_IN_SET() je peux filtrer pour ne garder que les dossier dont l'utilisateur possède un accès de type "A" ou un "D" équivalent:

Code : Tout sélectionner

HAVING FIND_IN_SET('A', types)
J'ai oublié de le préciser plus tôt, mais le but ultime étant d'utiliser le résultat sous la forme d'une table dérivée, jointe à la table des dossiers, par exemple

Code : Tout sélectionner

SELECT d.* FROM ( SELECT id_dossier, CASE GROUP_CONCAT(DISTINCT type_user ORDER BY type_user) WHEN 'A,B,C' THEN 'A,B,C' WHEN 'A,B' THEN 'A,B' WHEN 'A,B,D' THEN 'A,B,C' WHEN 'B,C,D' THEN 'A,B,C' WHEN 'B,D' THEN 'A,B' WHEN 'A,B,C,D' THEN 'A,B,C' ELSE 'INVALIDE' END AS types FROM liaison_dossier WHERE id_user = 'XXX' GROUP BY id_dossier HAVING FIND_IN_SET('A', types) ) AS ld JOIN dossiers d USING (id_dossier)

par p_m_g » 23 nov. 2007, 16:06

en cherchant un peu j'ai réussi à établir la requete suivante :
SELECT l1.id_dossier
FROM dossiers AS l1
LEFT JOIN liaison_dossier AS l2 ON ( l1.id_dossier = l2.id_dossier
AND l2.type_user = 'A' )
LEFT JOIN liaison_dossier AS l3 ON ( l1.id_dossier = l3.id_dossier
AND l3.type_user = 'B' )
LEFT JOIN liaison_dossier AS l4 ON ( l1.id_dossier = l4.id_dossier
AND l4.type_user = 'C' )
WHERE l2.id_user = 'xxx'
OR ( l3.id_user = 'xxx'
AND l4.type_user IS NOT NULL
AND l2.type_user IS NULL )
cependant, le temps d'affichage s'ajoutant aux autres requetes est beaucoup plus long.
d'autant plus que je fais une requete ensuite (la meme) ou je boucle pour l'affichage mais avec des tranches de 30 resultats pour de l'affichage page à page.
je fais donc 2 fois la meme requete.

y aurait-il une requete plus optimisée ?

Merci

par p_m_g » 22 nov. 2007, 11:33

Merci déja pour cette réponse fournie, je vois que l'on avance.

Pour information, et ceci est bien à prendre en compte, je n'ai pas la main sur la gestion sur les flux des données. C'est une connexion de serveur à serveur qui s'effectue avec simplement de l'insert. le formatage des données est géré en amont avant l'envoi.
ensuite, ma partie c'est l'affichage.
# c'est étrange de voir un "id" en VARCHAR, si ces "id" sont des nombres il vaudrait mieux utiliser le type INT (plus rapide, plus compacte, ne risque pas d'insérer autre chose qu'un nombre, n'est pas vulnérable à un espace mal placé, etc...)
ce ne sont pas que des nombres, il peut y avoir des lettres (pour id_dossier).
pour id_user, oui ce sont des nombres.
# pourquoi "type_user" est-il un CHAR(3) ? N'est-il pas destiné à stocker une seule lettre à la fois ? Auquel cas un CHAR(1) est plus adapté voire mieux encore, un ENUM('A','B','C','D')
ce champ est rempli a chaque fois de 3 lettres, j'ai pris a,b,c... pour l'exemple.
dois-je laisser char(3) ?
# il faudrait un index sur (id_user,type_user)
j'ai un index sur id_dossier dans la table liaison, et une clé primaire sur id_user dans la table users et sur id_dossier dans la table dossiers.
afin d'optimiser les temps de recherche, que dois-je faire au niveau des index, des clés primaires ?
je dois mettre des index sur id_dossier et id_user sur les 3 tables ?
ceci est important car les tables pourront monter à 500 000 enregistrements pour chaque bases.
Idéalement, il vaudrait mieux avoir un index UNIQUE sur (id_user,type_user,id_dossier) à la place, pour garantir qu'il n'y a pas de doublons (il améliorerait les performances également). D'un autre côté, les VARCHAR prennent dans un index autant de place que le CHAR équivalent donc la taille de ton index serait supérieur à celle de tes données. À toi de voir quelle importance tu accordes à la propreté des données.
chaque dossier et chaque utilisateur est unique. ensuite dans la table liaison le meme utilisateur peut apparaitre plusieurs fois (s'il appartient à plusieurs dossiers) avec un type différent (ex. ci-dessous).

liaison_dossier (id_dossier-type_user-id_user):
==============================
v123 - A - 1
v123 - B - 2
V123 - C - 3
V124 - A - 5
V124 - B - 4
V124 - D - 1
v125 - A - 3
v125 - B - 2
v125 - C - 6
SELECT d.*
FROM (
SELECT DISTINCT id_dossier
FROM liaison_dossier
WHERE id_user = 'XXX'
AND type_user = 'C'
) AS ld
JOIN dossiers d USING (id_dossier)
cette requete n'est pas possible compte tenu de mon exemple ci-dessus ?
Maintenant, concernant le reste du sujet... je n'ai pas vraiment compris comment fonctionnait le type D. Apparemment il est considéré comme un A s'il n'y a pas de A, comme un C s'il n'y a pas de C et est ignoré dans tous les autres cas ? Ne pourrais-tu pas créer un TRIGGER pour automatiquement modifier sa valeur ? Par exemple, quelqu'un insère un D, il n'y a pas de A alors tu remplaces automatiquement le D.
je ne peux pas modifier les données, il faut que je fasse avec. il faut que je gère l'affichage à la volée.
Sinon, pour déterminer la valeur de D il va falloir faire plusieurs auto-jointures pour savoir si c'est un A, un B ou rien du tout. Est-ce que ta liste de 6 cas est exhaustive ? Pas de "A-D", "D", "A-C-D" ?
oui ce sont tous les cas que l'on peut rencontrer.

au niveau de la procédure actuelle que j'ai mis en place (avant la venue du D), je recherche tous les dossiers ou l'utilisateur est présent pour un type donné. ensuite dans la boucle je vais chercher pour chaque dossier les autres utilisateurs présent pour les afficher.
imaginons un dossier ou on trouve 3 utilisateurs de type A-B-C. je suis A donc je 'ai vais aller chercher les infos sur B et C.

Merci d'avance pour les infos.

par Hubert Roksor » 21 nov. 2007, 22:57

Concernant le schéma de la base
  • c'est étrange de voir un "id" en VARCHAR, si ces "id" sont des nombres il vaudrait mieux utiliser le type INT (plus rapide, plus compacte, ne risque pas d'insérer autre chose qu'un nombre, n'est pas vulnérable à un espace mal placé, etc...)
  • pourquoi "type_user" est-il un CHAR(3) ? N'est-il pas destiné à stocker une seule lettre à la fois ? Auquel cas un CHAR(1) est plus adapté voire mieux encore, un ENUM('A','B','C','D')
  • il faudrait un index sur (id_user,type_user)

    Code : Tout sélectionner

    CREATE INDEX type_par_user ON liaison_dossier (id_user, type_user)
    Idéalement, il vaudrait mieux avoir un index UNIQUE sur (id_user,type_user,id_dossier) à la place, pour garantir qu'il n'y a pas de doublons (il améliorerait les performances également). D'un autre côté, les VARCHAR prennent dans un index autant de place que le CHAR équivalent donc la taille de ton index serait supérieur à celle de tes données. À toi de voir quelle importance tu accordes à la propreté des données.
Concernant la première requête, tu utilises un LEFT JOIN à la place d'un [INNER] JOIN. LEFT JOIN n'est à utiliser que lorsqu'une correspondance n'existe pas forcément de l'autre côté de la jointure. Ce n'est pas le cas ici, donc il faut remplacer par un JOIN (en réalité, MySQL le détecte lui-même et corrige ta requête silencieusement). L'ordre des tables n'est pas le bon non plus. "liaison_dossier" est comme l'index d'un livre, et "dossiers" est le contenu du livre. Quand tu cherches un chapître, tu cherches d'abord dans l'index puis tu vas regarder à la page correspondante. Ici c'est pareil et "liaison_dossier" devrait être en premier

Code : Tout sélectionner

SELECT d.* FROM liaison_dossier ld JOIN dossiers d USING (id_dossier) WHERE ld.id_user = 'XXX' AND ld.type_user = 'C' GROUP BY ld.id_dossier
Si tu as ajouté l'index UNIQUE sur les 3 colonnes, le GROUP BY est inutile. Alternativement, selon la taille de ta base, tu peux essayer de t'y prendre autrement :

Code : Tout sélectionner

SELECT d.* FROM ( SELECT DISTINCT id_dossier FROM liaison_dossier WHERE id_user = 'XXX' AND type_user = 'C' ) AS ld JOIN dossiers d USING (id_dossier)
Maintenant, concernant le reste du sujet... je n'ai pas vraiment compris comment fonctionnait le type D. Apparemment il est considéré comme un A s'il n'y a pas de A, comme un C s'il n'y a pas de C et est ignoré dans tous les autres cas ? Ne pourrais-tu pas créer un TRIGGER pour automatiquement modifier sa valeur ? Par exemple, quelqu'un insère un D, il n'y a pas de A alors tu remplaces automatiquement le D.

Sinon, pour déterminer la valeur de D il va falloir faire plusieurs auto-jointures pour savoir si c'est un A, un B ou rien du tout. Est-ce que ta liste de 6 cas est exhaustive ? Pas de "A-D", "D", "A-C-D" ?

par p_m_g » 21 nov. 2007, 19:54

en fait les données sont envoyés sur nos serveurs et mis à jour en live, ce sont des données extraites de logiciels. c'est pour de la consultation en ligne de dossier.

c'est pour cela que la structure des tables et les données ne sont pas modifiables et je suis d'accord avec vous qu'il aurait été préférable de gérer cela en amont.

maintenant, il faut que j'adapte mes requetes suivant ce que j'ai indiqué.

il doit bien y avoir une solution de requetes imbriquées ou autre, non ?

Merci encore.

par sadeq » 21 nov. 2007, 19:01

Question: A quoi sert vraiment le type D ? et comment fais-tu pour noter que D est concidéré comme un autre type dans tes tables :-k

Requete conditionelle complexe (mysql)

par p_m_g » 21 nov. 2007, 17:01

Bonjour,

voici les données du problème (tables non modifiables):

voila mes 3 tables simplifiées pour l'exemple (les liaisons se font sur id_dossier et id_user) :

# [dossiers] -> (id_dossier, nom_dossier)

# [liaison_dossier] -> (id_user, id_dossier, type_user)

# [users] -> (id_user, nom_user)

il existe 3 type d'utilisateurs (type_user) => A, B, et C

chaque utilisateur se connecte sur 1 type par defaut et ne verra que certaines données en fonction de son type.
mais il aura la possiblité de basculer sur 1 autre type à tout moment s'il existe sur 1 ou plusieurs autre type.

le but étant d'afficher la liste des dossiers ou l'utilisateur est présent en fonction de son type.
ex. je me connecte en type C, donc je vais afficher tous les dossiers où je suis présent en tant qu'utilisateur de type C

jusque là j'arrive bien a faire ma requete (ex. ci-dessous) :

-> je retourne les dossiers ou je suis présent en tant que type C :

Code : Tout sélectionner

SELECT dossiers.* FROM dossiers LEFT JOIN liaison_dossier ON dossiers.id_dossier = liaison_dossier.id_dossier WHERE liaison_dossier.id_user = 'XXX' AND type_user = 'C' GROUP BY liaison_dossier.id_dossier
ceci fonctionne bien (dites moi si on peut faire mieux au niveau de la requete).

seulement c'etait trop beau pour continuer et un autre type (D) vient s'ajouter et c'est là que ça se complique.
en effet, ce dernier peut prendre la valeur des autres types.

voici les cas de figure sur un dossier avant le nouveau type (D):

1 - A-B-C
2 - A-B

voici tous les cas maintenant :

1: A-B-C
2: A-B
3: A-B-D (D est considéré comme C)
4: B-C-D (D est considéré comme A)
5: B-D (D est considéré comme A)
6: A-B-C-D (on ne tient pas compte de D)

je suis A => je suis dans les cas 1,2,3,4,5 et 6 (tous).
je suis B => idem
je suis C => je suis dans les cas 1,3,4 et 6
je suis D => aucun acces pour lui (il sera visible par les autres selon les cas)

les utilisateurs de type D n'apparaissent jamais dans les dossiers, sauf dans les cas où ils sont considérés autrement (A,B ou C).
ex. je me connecte en tant que type C je dois avoir les liste des dossiers ou je suis présent (les cas 1,3,4 et 6).

j'espère que vous avez suivi le principe.

la difficulté est de trouver une requete en prenant compte des différents cas.
avec les requetes plus haut, j'affiche seulement le cas 1 et pas les autres notamment le 3.

voici un exemple type :
ex. afficher mes dossiers (id_user) ou mon type est C et D si A est absent
ainsi on affichera les cas 1,3,4 et 6.

Merci ces précieuses infos.

Code : Tout sélectionner

CREATE TABLE dossiers ( id_dossier varchar(20) NOT NULL default '', nom_dossier varchar(90) NOT NULL default '', PRIMARY KEY (id_dossier) ) TYPE=MyISAM;

Code : Tout sélectionner

CREATE TABLE liaison_dossier ( id_user varchar(10) NOT NULL default '', type_user char(3) NOT NULL default '', id_dossier varchar(20) NOT NULL default '', KEY id_dossier (id_dossier) ) TYPE=MyISAM;

Code : Tout sélectionner

CREATE TABLE users ( id_user varchar(10) NOT NULL default '', nom_user varchar(60) NOT NULL default '', PRIMARY KEY (id_user) ) TYPE=MyISAM;