Question pour expert en jointures

Eléphant du PHP | 152 Messages

23 avr. 2007, 19:45

Bonjour à tous,

J'aimerais soulever deux ou trois problèmes de jointures dont on ne parle pas souvent.

Je vous le fait sous forme d'énoncé qui pourrait s'apparenter à la vie
courante comme ça ce sera clair pour tout le monde .


Problème 1 : Nous disposons d'une table clients, chaque client représente une société qui peut contenir plusieurs contacts, plusieurs adresses de facturations, et avec lequel nous pouvons naturellement travailler sur plusieurs dossiers.

Nous appelerons nos tables : clients, contacts, adresses, dossiers.

Nous définissons une architecture simpliste :
- clients.ID
- clients.NOCLIENT (jointure)
- clients.NOM

- contacts.ID
- contacts.NOCLIENT (jointure)
- contacts.NOM
- contacts.TEL

- adresses.ID
- adresses.NOCLIENT (jointure)
- adresses.ADR1
- adresses.ADR2
- adresses.CP
- adresses.VILLE

- dossiers.ID
- dossiers.NOCLIENT (jointure)
- dossiers.NOM

Question : Comment faire une requête qui nous permet de sélectionner
à la fois toutes les commandes d'un client, toute ses adresses et tout
ses contacts sans avoir de doublons.

En gros, lorsque nous faisons des jointures les une derriere les autres
avec des objets qui ont une relation d'appartenance avec d'autres
objets, pas de problème.
Il est facile de faire une jointure CLIENT -> DOSSIER -> FACTURE
quand on considère qu'une facture appartient à un dossier et qu'un
dossier appartient à un client . On peut dire que la facture appartient
au client.

Mais dans notre problème, le contact n'appartient pas nécéssairement
au dossier.

Si nous rédigeons notre requête avec une jointure externe de droite à gauche comme ceci : CLIENT -> DOSSIERS -> CONTACT

Nous obtiendrons un résultat comme ça :

Code : Tout sélectionner

clients.NOM | dossiers.NOM | contacts.NOM SARL BOB | XY12 | Mr duchemol SARL BOB | XY13 | Mr duchemol SARL BOB | XY12 | Mr bernard SARL BOB | XY13 | Mr bernard SARL BOB | XY12 | Mr jacques SARL BOB | XY13 | Mr jacques
Vous voyez le problème ?

L'idéal serait de pouvoir récuperer à partir d'une seule requête des variables PHP comme ceci :
$res['dossiers.NOM'][0] = XY12
$res['dossiers.NOM'][1] = XY13
$tab['contacts.NOM'][0] = Mr duchemol
$tab['contacts.NOM'][1] = Mr bernard
$tab['contacts.NOM'][2] = Mr Jacques
Pour ce faire nous aurions besoin d'une requête qui donnerai comme
resultat :
  • clients.NOM | dossiers.NOM | contacts.NOM
    SARL BOB | XY12 | Mr duchemol
    SARL BOB | XY13 | Mr bernard
    SARL BOB | NULL | Mr jacques
Car on se fout un peut de faire tomber en face les contacts et les dossiers
puisque dans notre situation il ne sont pas en relation.

Ainsi nous pourrions faire une boucle php sur chaque élément :
// LISTE LES CONTACTS D'UN CLIENT
$i=0;
while ($res['contacts.NOM'][$i] != "NULL")
{
        echo "<br>$res['contacts.NOM'][$i]";
        $i++;
}
// LISTE LES DOSSIERS D'UN CLIENT
$i=0;
while ($res['dossiers.NOM'][$i] != "NULL")
{
        echo "<br>$res['dossiers.NOM'][$i]";
        $i++;
}

Résultats que nous devrions obtenir avec les adresses en plus :
  • clients.NOM | dossiers.NOM | contacts.NOM | adresses.CP
    SARL BOB | XY12 | Mr duchemol | 75015
    SARL BOB | XY13 | Mr bernard | NULL
    SARL BOB | NULL | Mr jacques | NULL
Alors dans un premier temps pouvez-vous me dire comment rédiger
ma requête, sur quoi je dois m'orienter (GROUP BY ??) ou peut-être
comment améliorer mes jointures pour obtenir le résultat final souhaité ?


Allez un deuxième tout petit problème de jointures pour la route :
Lorsque nous utilisons une jointure externe, même s'il n'existe
pas de champs correspondant dans la deuxième table, les champs
de la première table sont affichés et ceux de la deuxième transformés
en valeur NULL.

(ex basé sur architecture en haut de ce post : SELECT clients.NOM,
contacts.NOM FROM clients LEFT OUTER JOIN contacts ON
clients.NOCLIENT = contacts. NOCLIENT where
clients.NOM = 'SARL BOB' )

Dans l'hypothèse où l'on a pas enregistré de contact pour la SARL BOB
la reqûete avec jointure externe va retourner :
SARL BOB | NULL

Si l'on avait utilisé la jointure interne, la requête nous aurais retourné :
"RIEN DU TOUT"

Mon problème est de me retrouver avec une valeur NULL
au lieu d'avoir une valeur par défaut dans ma table.

Je souhaiterai obtenir par exemple :
SARL BOB | Aucun contact

Mais NULL c'est nul !! :lol:


Ma question est la suivante : Est ce que Mysql garde les valeurs par défaut
où est ce qu'il transforme systématiquement tout en NULL.
Ma question concerne tout les type de champs (ENUM surtout).


Merci de votre aide et désolé pour la lecture.

Eléphant du PHP | 152 Messages

23 avr. 2007, 19:59

Je ne pense pas que ce soit le bon endroit pour écrire ça
mais j'aimerai apporter ma contribution à PHP FRANCE après avoir
terminé mes recherches sur MYSQL car j'estime être redevable
envers ce site qui m'a souvent sorti du pétrain.

Pouvez-vous me dire si ça intéresse quelqu'un.

Les bases du cours serait :
- Débuter avec le SQL
- Approndir avec le SQL
- Créons ensemble une architecture BDD simple
- Créons ensemble une architecture BDD complexe
- Créons ensemble une architecture BDD complexe optimisé pour un fort traffic.
- Interfaçons notre architecture à l'aide des classes de PHP.

Dites moi, est ce que ça vous branche ?

(PS ma question s'adresse autant aux admins qu'aux utilisateurs).

Modérateur PHPfrance
Modérateur PHPfrance | 6373 Messages

23 avr. 2007, 20:58

Hop j'ai lu vite fait mais :
- pour remplacer les NULL éventuellement renvoyés par une jointure externe, tu as la fonction COALESCE

- toute proposition de contribution est bonne à prendre, je pense que des petits articles cours sur les questions qui reviennent souvent, et une mini démonstration des bases de la normalisation seraient utiles (même si ça existe déjà ailleurs). Est-ce que tu as déjà des choses à soumettre ?

Eléphant du PHP | 152 Messages

24 avr. 2007, 00:38

Je suis justement en train de travailler dessus.

Mais je suis plus parti du point de vue d'un utilisateur qui n'y connais
pas grand chose à l'informatique et qui veut devenir expert.

La tâche est très rude.

Je ne pense pas que le cours complet soit adapter à PHP FRANCE
Mais par contre la fin du cours oui.

En fait le plus important selon moi, c'est de travailler sur la mauvaise
approche que les débutants ont.

Je souhaite vraiment leur faire prendre conscience qu'il y a des
méthodes de programmation et de gestion BDD qui ne sont pas
beaucoup plus compliquées et qui pourront leur faire gagner
en temps de développement et en facilité de maintenance.

Il y a un énorme travail pédagogique à faire. Trop de programmeurs
se documentent à droite et à gauche pour répondre à un besoin précis.

Je pense que leur temps d'apprentissage pourrait être réduit de moitié
si nous prenions la peine de rédiger un cours qui donne vraiment envie
d'être lu jusqu'au bout.

La structure de mon cours pour l'instant est la suivante :
Une intro qui explique le fonctionnement des réseaux,
protocoles, languages et bdd avec des exemples amusants.

1 . Le HTML version basique
2 . Configuration serveur
2 . Une première approche du PHP
3 . Retour sur le HTML + CSS
4 . Une seconde approche du PHP avec Mysql
5 . Approndir SQL
6 . Approndir PHP
7 . Petits exemples combinant les classes et Mysql
8 . Créons ensemble une architecture BDD simple
9 . Créons ensemble une architecture BDD complexe
10 . Créons ensemble une architecture BDD complexe optimisé pour un fort traffic.
11 . Créons ensemble une Interface pour notre architecture à l'aide des classes de PHP.


Vous avez déjà un aperçu des thèmes, j'expliquerai les méthodologies
à appliquer pour analyser les situations de la vie courante et les
retranscrire sous forme de classe et sous forme de schémas BDD.

J'expliquerais l'utilité, à l'aide de contre-exemples, de passer du temps
à coder proprement, respecter les normes et schématiser le mieux
possible ses programmes.

Je pense que pour PHP FRANCE les points (10 et 11) seront les
mieux adapté.

J'ai déjà rédigé une dizaine de pages de cours, ça devrait allez assez
vite. Je vous envoi un lien rapidement pour lecture.

P.S. J'ai encore un travail d'apprentissage sur le SQL à faire sur moi même. Je dois encore maitriser les jointures et les clauses GROUP BY ET HAVING.

Merci à vous.
Faîtes pas attention aux fautes d'orthographes, je ferais relire mon
travail avant de le proposer.

Mammouth du PHP | 19672 Messages

24 avr. 2007, 08:02

L'idée est bonne, j'ajouterais toutefois un point : lorsqu'on veut transmettre un savoir, il m'apparaît important de ne pas négliger un détail trop souvent oublié. Lorsque nous avons nous-même appris, nous avons buté sur des obstacles et nous les avons franchi : oublier ces obstacles, c'est oublier que tout débutant va passer par le même chemin et éprouver les mêmes difficultés. Donc il est important de ne pas l'oublier : ce qui pour certain est devenu très évident peut être complètement abscons pour un néophyte.

Actuellement, je n'ai absolument pas de temps pour ça et je n'ai pas de connexion Internet chez moi pour quelques semaines, mais quand ma situation sera stabilisée de ce coté là, je pourrai peut-être apporter ma pierre à cet édifice ;)
Codez en pensant que celui qui maintiendra votre code est un psychopathe qui connait votre adresse :axe:

Mammouth du PHP | 693 Messages

24 avr. 2007, 13:40

Je pense que tout le cours a sa place sur PHP FRANCE, car il y a beaucoup de type de personnes qui l'utilise, du débutants qui ne comprend pas les instructions if... else, aux professionnelles qui bloquent sur des détails pointus.

Eléphant du PHP | 152 Messages

24 avr. 2007, 19:12

C'est encore moi,

Avec toutes ces discussions, personnes n'a pu répondre à ma
première question .

Comment faire une jointure qui élimine les doublons (voir exemple
plus haut) ?

Merci à vous :wink:

Administrateur PHPfrance
Administrateur PHPfrance | 3088 Messages

24 avr. 2007, 20:14

Just pour dire que le topic a l'air intéressant mais un peu TL;DR, à tel point que je ne savais plus si c'était une proposition pour un tutoriel ou s'il y avait une question non-rhétorique.

Concernant la proposition de tutoriel, bien sûr on est ouvert à tout mais je pense qu'il faudrait le faire en collaboration et/ou sous la supervision d'un ViPHP. Pas seulement parce que le violet c'est cool, mais aussi pour l'adapter au ton des autres FAQ tout en évitant les doublons avec les topics existants. Et il est bien connu que tous les ViPHP connaissent toutes les FAQ par cœur ;)

À part ça, je repasserai dans la soirée pour le reste, promis ;)

PS: pour ta question, un schéma des tables serait le bienvenu svp merci :)

Eléphant du PHP | 152 Messages

24 avr. 2007, 22:17

Ok ! J'attend ta réponse alors !! :D

Administrateur PHPfrance
Administrateur PHPfrance | 3088 Messages

24 avr. 2007, 22:22

Comment faire une requête qui nous permet de sélectionner à la fois toutes les commandes d'un client, toute ses adresses et tout ses contacts sans avoir de doublons.
Il y certainement un moyen, mais si toutes ces données ne sont pas liées entre elles alors il vaut mieux effectuer des requêtes séparées. D'ailleurs, un bon indicateur pour savoir s'il faut séparer une requête en plusieurs c'est la redondance. Si un enregistrement est retourné plusieurs fois par la même requête c'est qu'il y a peut-être un problème (dans ton exemple, toutes les lignes retournées contiennent "SARL BOB"). Pareil pour les NULL, c'est généralement l'indication que la relation entre les données est faible et que la requête pourrait être scindée.

Dans le cas présent, le plus simple/pratique/efficace est de requêter les adresses du client, les contacts du clients puis toutes ses commandes (ou dans un autre ordre si tu préfères, selon le traitement que tu leur appliques). Il n'y a pas d'intérêt pratique à essayer de tout condenser en une seule requête, c'est plus compliqué donc plus propisce à l'erreur, et également moins performant.
Est ce que Mysql garde les valeurs par défaut où est ce qu'il transforme systématiquement tout en NULL
Je ne suis pas sûr de comprendre. Si la colonne existe, MySQL renvoit sa valeur, sinon il renvoit NULL. Attention, j'ai vu que tu utilisais la chaîne PHP "NULL". Normalement, la valeur NULL (ou plutôt, l'absence de valeur) est représentée en PHP par la constantes NULL. Tu peux le vérifier en faisant un var_dump()
var_dump($res, null, 'NULL');
Au fait, j'ai lu dans un autre topic que tu utilisais "MySQL 4.2" (sic). L'embêtant c'est qu'à ma connaissance une telle version n'existe pas, donc tu dois avoir confondu avec quelque chose d'autre.

Autre chose, je le répète régulièrement, je recommande fortement de maintenir l'unicité des noms de colonnes. Une même donnée doit toujours être représentée par le même nom de colonne, et deux colonnes de même noms doivent toujours représenter les mêmes données. Par exemple, dans ta base les colonnes "NOM" peuvent représenter le nom d'un client, le nom d'un contact ou encore le nom d'un dossier. C'est pour cela que je recommande de préfixer les noms de colonnes par le type d'information qu'elles contiennent. Par exemple "client_nom", "contact_nom", "dossier_titre" (pour moi, seuls les personnes portent des noms mais peu importe ce avec quoi tu te sens plus à l'aise). Ça évite entre autres choses à avoir à utiliser des aliases lorsqu'on récupère les contacts de chaque client, et ça limite les risque d'erreurs lorsqu'on traite les résultats en PHP.

Eléphant du PHP | 152 Messages

24 avr. 2007, 23:06

Pour ce qui est des noms de colonnes, j'utilise des alias.
Pour ce qui est de la version de Mysql, en effet je me suis trompé,
j'utilise la version 4.0.18.
Je ne suis pas sûr de comprendre. Si la colonne existe, MySQL renvoit sa valeur, sinon il renvoit NULL
Une jointure externe renvoit un resultat même si elle ne trouve
pas de correspondance sur le champ en relation .

Alors qu'une jointure interne ne renverrai rien.

Je vais prendre un cas simple :
SELECT clients.NOM, dossiers.SUJET, adresses.VILLE from clients
LEFT OUTER JOIN dossiers ON clients.NOCLIENT=dossiers.NOCLIENT
LEFT OUTER JOIN adresses ON clients.NOCLIENT=adresses.NOCLIENT.

Une requête de ce type peut par exemple me retouner ça :
NOM | SUJET | CP
bob AB 75000
bob NULL 92000

Dans cet exemple j'ai un doublon sur bob
En resultat souhaité j'accepte bob comme doublon puisque
c'est la référence dans ma jointure et ça se sert à rien de le remplacer
par NULL.

Dans le cas de figure où il y a plus d'éléments à droite qu'à gauche,
pas de problème.

Supposons maintenant que deux dossiers on été fais pour bob
et que bob possède deux codes postaux
On se retrouverait avec ceci :
NOM | SUJET | CP
bob AB 75000
bob AB 92000
bob CD 75000
bob CD 92000


Vous voyez le problème ?


Contrairement à toi Hubert Roksor, je pense que des jointures
bien soignées accelèrent le traitement puisqu'elle permettent
d'éviter pas mal de boucles.

Elle permettront aussi, une fois que j'aurais trouvé l'astuce
de tisser de multiples relation et de segmenter le mieux possible
les propriétés de mes obets.

Imagines une table client en relation avec toutes ces tables.
Avoirs
Devis
Factures
Commandes
Dossiers
Employés
Adresses
Contacts

Et on te dis, je veux tout savoir sur ce client ??
req
While ()
req
While()
req
While()

Si je trouve la solution avant vous, je la poste !! promis !!
J'essayerais également de faire un test de benchmark entre
les while et les jointures.

A+

Administrateur PHPfrance
Administrateur PHPfrance | 3088 Messages

24 avr. 2007, 23:44

Une jointure externe renvoit un resultat même si elle ne trouve pas de correspondance sur le champ en relation .
Sauf si quelque chose s'est perdu dans la traduction, je suis obligé de réaffirmer qu'une jointure externe (LEFT [OUTER] JOIN) renvoit NULL s'il n'y a pas de correspondance. Et PHP fait la différence entre la valeur NULL et la chaîne de caractères 'NULL'. Donc attention, NULL n'est pas une chaîne de caractères. Voir var_dump() et les types de données dans le manuel.
Vous voyez le problème ?
Ben oui, le problème c'est que tu essaies de tout récupèrer en une fois alors qu'il n'y a pas de relation entre les dossiers et les codes postaux de Bob. C'est typiquement un exemple où tu devrais récupérer les informations pertinentes à Bob d'une part puis les informations pertinentes aux dossiers de l'autre.
Contrairement à toi Hubert Roksor, je pense que des jointures bien soignées accelèrent le traitement puisqu'elle permettent d'éviter pas mal de boucles.
Le plus simple pour toi serait peut-être d'effectuer de simples mesures chronométrées, tu verrais que cela n'accélère jamais le traitement des données. Parmis les incoviénents on peut citer la quantité de données inutiles que le serveur a à filtrer, qu'il a à envoyer au client, que le client a à parser, que PHP met pour peupler le tableau, etc... Au final on perd beaucoup de temps. Accessoirement les requêtes séparées ont un meilleur taux d'efficacité au niveau du cache interne de requêtes. Il y a aussi tout un tas de calculs liés à la jointure que je ne peux malheureusement pas expliquer parce que je les connais trop mal. Tout ça pour dire que les jointures coûtent chers et doivent être évitées lorsqu'elles ne sont pas requises.
Si je trouve la solution avant vous, je la poste !!
Si tu veux je peux te la donner tout de suite : utilise GROUP_CONCAT() avec un uniqid() en séparateur et tu groupes tout sur NOCLIENT. Ensuite tu démèles tout en PHP. Mais ça reste une manière très compliquée de faire quelque chose de très simple. Pourquoi ne pas créer une fonction PHP tout simple ?
function get_client($client_id, $options = array())
{
	$sql = 'SELECT * FROM clients WHERE client_id = ' . $client_id;
	$result = mysql_query($sql, DB);
	$client = mysql_fetch_assoc($result);

	foreach ($options as $table)
	{
		$sql = 'SELECT * FROM ' . $table . ' WHERE client_id = ' . $client_id;
		$result = mysql_query($sql, DB);

		$client[$table] = array();
		while ($row = mysql_fetch_assoc($result))
		{
			$client[$table][] = $row;
		}
		mysql_free_result($result);
	}

	return $client;
}

var_dump(get_client(123, array('contacts', 'adresses')));
25 lignes plus tard tu as une fonction qui peut récupérer toute information pertinente à un client en un appel. Et si tu fais quelques tests tu verras que c'est très rapide (toutes les tables ont bien sûr un index sur "client_id").

Eléphant du PHP | 152 Messages

24 avr. 2007, 23:58

Merci pour la piste group_concat le tuto mysql a l'air pas mal,
et merci aussi pour ta fonction car j'avoue que j'aurais pas pensé
à mettre les tables en paramètre.

:D