Le langage SQL-DML

Il arrive fréquemment que les informations recherchées se trouvent dans plusieurs tables différentes. Or, a priori, une requête SQL de recherche ne permet d'extraire des informations qu'à partir d'une seule table. Comment est-il donc possible d'obtenir, par exemple, la liste suivante des clients ayant passé des commandes associés aux numéros des commandes qu'ils ont passées ?

sql80.jpg

Liste des clients ayant passé une commande associés aux numéros de leurs commandes

Nous pouvons certes obtenir, à partir de la table Commande, la liste des identifiants des commandes ainsi que celle des identifiants des clients les ayant passées puis comparer chaque identifiant des clients à son nom respectif à l'aide d'une liste issue de la table Client comme le propose les requêtes suivantes:

sql81.jpg

Liste des identifiants des commandes et des clients les ayant passées

sql83.jpg

Liste des identifiants des clients avec leur nom correspondant

La table que nous cherchons à obtenir est une combinaison des informations des deux tables Commande et Client. Une telle combinaison est possible grâce au concept de jointure de tables.

Jointure de tables

Les jointures permettent d'exploiter pleinement le modèle relationnel des tables d'une base de données. Elles sont faites pour mettre en relation deux tables concourant à rechercher la réponse à des interrogations. Une jointure permet de combiner les colonnes de plusieurs tables grâce à une condition de jointure basée sur l'égalité des valeurs entre les colonnes communes. Illustrons pas à pas ce concept avec la requête permettant de récupérer la liste des clients ayant passé une commande associés aux numéros des commandes qu'ils ont passées.

Remarquons tout d'abord que le langage SQL permet d'effectuer le produit cartésien des lignes de deux tables. La requête suivante permet par exemple de faire le produit cartésien des tables Client et Commande, c'est-à-dire de retourner toutes les combinaisons possibles des lignes de la table Client avec celles de la table Commande:

sql85.jpg

Produit cartésien des tables Client et Commande

La table résultant de la requête ci-dessus comporte 150 lignes: elle combine les 15 lignes de la table Client avec les 10 lignes de la table Commande. Ainsi, nous pouvons constater que la ligne correspondant à l'enregistrement Archambault de la table Client a été combinée dans la table résultat avec la ligne des données de la commande 1001 tirée de la table Commande, puis avec la ligne des données de la commande 1002, puis 1003, 1004, 1005, etc, ce que donne 10 lignes de combinaison pour Archambault. Des combinaisons analogues ont été faites pour chacun des 15 clients de la table Client.

N'ayant besoin que d'un nombre restreint d'informations issues des colonnes des tables Client et Commande, nous pouvons demander au SGBD, avant même qu'il ne procède a produit cartésien des deux tables, de ne sélectionner que certaines colonnes dans chacune des deux tables et de ne procéder ainsi au produit cartésien de deux tables de taille réduite:

sql87.jpg

Produit cartésien de sous-tables des tables Client et Commande

Le résultat engendré par la requête ci-dessus est toujours une table de 150 lignes mais ne contenant plus que les colonnes nous intéressant. Cette table de résultat contient bel et bien les informations que nous recherchons: nous observons par exemple qu'Archambault a passé deux commandes: la 1001 et la 1010. En effet, Archambault est le client répondant à l'identifiant 1 (Client.ClientID = 1). Or, la commande 1001 (Commande.CommandeID=1001) a été passé précisément par le client ayant l'identifiant 1, c'est-à-dire Archambault. Il en va de même pour la commande 1010. Les autres lignes de la table résultat n'ont aucun intérêt.

Finalement, seules nous intéressent les lignes dans lesquelles l'identifiant du client issu de la table Client a la même valeur que l'identifiant du client ayant passé une commande, que l'on retrouve dans la table Commande. Formulé plus synthétiquement, les lignes de la table des résultats contenant les informations désirées sont celles pour lesquelles la condition de jointure Client.ClientID = Commande.ClientID est remplie:

sql89.jpg

Jointure des tables Client et Commande basée sur la condition Client.ClientID = Commande.ClientID

La condition de jointure Client.ClientID = Commande.ClientID permet de filtrer les lignes issues du produit cartésien des deux tables Client et Commande et n'en retenir ainsi dans la table des résultats que les lignes pertinentes. Une telle requête SQL demande au SGBD de faire une opération de jointure, c'est-à-dire un produit cartésien conditionnel des deux tables. Les colonnes commune ClientID sont utilisées dans le critère de jointure pour rapprocher les deux tables. L'affichage redondant des numéros de clients identiques, extraits des tables Client et Commande, prouvent que ces numéros se correspondent et que cette mise en correspondance produit une ligne par commande passée par le client en question.

La requête ci-dessus peut être quelque peu simplifiée. En effet, jusqu'à présent, dans une requête faisant appel aux informations de plusieurs tables, nous avons à chaque fois précédé le nom de la colonne invoquée par le nom de la table contenant cette colonne suivi d'un point (par exemple, Client.Nom pour récupérer les informations situées dans la colonne Nom de la table Client). Cette manière de faire permet au SGBD de savoir dans quelle table se trouve la colonne à laquelle nous faisons référence.

Ainsi, lorsque deux colonnes (ou plus) portent le même nom dans l'ensemble des tables impliquées par la requête SQL, ce procédé permet d'indiquer précisément au SGBD laquelle des colonnes doit être manipulée et éliminer par conséquent toute ambiguïté: dans l'exemple précédent, lors de l'exécution de la requête, le SGBD doit savoir exactement laquelle des deux colonnes ClientID doit être manipulée, d'où l'importance de qualifier chaque référence à cette colonne par le nom de la table d'origine (Client.ClientID ou Commande.ClientID).

sql91.jpg

Requête SQL présentant une ambiguïté au niveau de la qualification de la colonne ClientID

Cependant, si dans l'ensemble des tables visées par notre requête il n'existe qu'une seule colonne ayant un nom auquel nous faisons référence, il n'est pas nécessaire de rajouter le préfixe du nom de la table dans laquelle se trouve la colonne: dans l'exemple précédent, il n'existe qu'une seule colonne dénommée Nom sur l'ensemble des tables Client et Commande. Il n'est dès lors pas obligatoire de précéder cette colonne du nom de la table Client dans laquelle elle se trouve même si cette formulation plus longue est acceptée: le SGBD comprendra sans ambiguïté à quelle colonne nous faisons référence.

Compte tenu de ce qui précède, nous pouvons simplifier notre requête initiale de la manière suivante:

sql93.jpg

Jointure des tables Client et Commande basée sur la condition Client.ClientID = Commande.ClientID

Finalement, si nous ne souhaitons pas voir dans la table des résultats les valeurs utilisées comme condition de jointure, il suffit de ne pas faire figurer après la clause SELECT les noms des colonnes les contenant. En effet, la clause WHERE peut faire appel à des colonnes ne figurant pas après la clause SELECT. Nous pouvons ainsi simplifier notre requête précédente comme suit:

sql94.jpg

Jointure des tables Client et Commande basée sur la condition Client.ClientID = Commande.ClientID

Cette dernière requête produit exactement le résultat que nous désirions initialement ! Un même résultat peut également s'obtenir à l'aide de la requête suivante:

sql95.jpg

Jointure des tables Client et Commande basée sur la condition Client.ClientID = Commande.ClientID

Le langage SQL étant un langage ensembliste, la clause INNER JOIN... ON... consiste à rapprocher les deux ensembles Client et Commande et à concaténer les lignes de Client et Commande qui ont des numéros de client identiques. Il s'agit de l'intersection des ensembles suivie de la concaténation des lignes correspondantes.

Jointure multiple

La puissance du modèle relationnel réside dans la possibilité de relier des objets distincts (client, commande, produit) en joignant les tables pour générer des informations à l'usage des décideurs ou des gestionnaires d'une entreprise. A titre d'exemple, considérons la requête suivante permettant de récupérer toutes les données nécessaires pour établir une facture de la commande 1006, à savoir:

Une telle requête nécessite la jointure de quatre tables:

sql96.jpg

Jointure de tables extrayant les informations nécessaires à la facturation de la commande 1006

Comme nous pouvons le constater les jointures de la requête ci-dessus reposent sur trois conditions:

  1. Client.ClientID = Commande.ClientID permet de relier chaque client à ses commandes;
  2. Commande.CommandeID = LigneCommande.CommandeID permet de relier chaque commande aux lignes de détail des produits commandés;
  3. LigneCommande.ProduitID=Produit.ProduitID permet de relier chaque ligne de détail à la description et au prix unitaire du produit correspondant.

La table résultat présente 12 colonnes selon l'ordre des attributs cités dans la clause SELECT. Remarquons que la dernière colonne calcule le prix à payer pour chaque produit commandé en fonction de la quantité commandée et du prix unitaire du produit en question.

A vous de jouer !

Rédigez les requêtes SQL suivantes permettant d'obtenir des informations à partir de la base de données Librairie. Vos solutions devront engendrer les mêmes tables que celles proposées dans le lien "Résultat" situé à la suite de l'énoncé de chaque requête:

  1. La requête retourne la liste des clients ayant passé une commande après le 31 août 2011
    ( Résultat | Solution)
  2. La requête retourne le nombre de clients ayant déjà passé une commande auprès du fournisseur
    ( Résultat | Solution)
  3. La requête retourne la liste des produits commandés par le client 'Archambault' en indiquant pour chaque produit le nombre d'exemplaires commandés
    ( Résultat | Solution)
  4. La requête retourne le classement des clients en fonction du nombre total d'exemplaires commandés, tous produits confondus, triés du meilleur au moins bon client actif
    ( Résultat | Solution)
  5. La requête retourne la liste décroissante des produits les plus commandés en indiquant pour chacun le nombre d'exemplaires commandés
    ( Résultat | Solution)