[[03_ Opérations entre les enregistrements]] # Requêtes sur deux tables ## Introduction Jusqu'à présent, nous avons appris à interroger des données au sein d'une seule table. Cependant, la puissance des bases de données relationnelles réside dans leur capacité à organiser les informations en plusieurs tables interconnectées, évitant ainsi la redondance et garantissant l'intégrité des données. Pour exploiter pleinement cette architecture, il devient indispensable de pouvoir combiner des données provenant de différentes tables. Ce chapitre vous introduira aux concepts fondamentaux permettant de lier et d'interroger des informations réparties sur deux tables. Nous explorerons les mécanismes qui sous-tendent ces opérations, en commençant par le concept de produit cartésien, pour ensuite aborder les différentes formes de jointures qui constituent le cœur des requêtes multi-tables en SQL. ### Objectifs du chapitre À la fin de ce chapitre, vous serez capable de : - Comprendre le concept de produit cartésien et son implication. - Utiliser la jointure interne (`INNER JOIN`) pour combiner des données correspondantes. - Utiliser les jointures externes (`LEFT JOIN` et `RIGHT JOIN`) pour inclure des données non correspondantes. - Choisir la jointure appropriée en fonction des besoins de votre requête. ### Pré-requis Pour aborder ce chapitre dans les meilleures conditions, vous devez maîtriser les concepts suivants : - Les bases du langage SQL (`SELECT`, `FROM`, `WHERE`, `ORDER BY`). - La structure d'une base de données relationnelle (tables, champs, enregistrements, clés primaires, clés étrangères). - Les types de données SQL. --- ## 1. La Nécessité de Combiner les Données Dans un système de gestion de base de données relationnelle (SGBDR), les informations sont organisées en tables distinctes pour des raisons de normalisation. Cette approche permet de : - **Réduire la redondance des données** : Chaque information est stockée une seule fois. - **Améliorer l'intégrité des données** : Les modifications n'ont lieu qu'à un seul endroit. - **Optimiser le stockage et les performances** : Des tables plus petites sont plus faciles à gérer. Cependant, cette séparation signifie que pour obtenir une vue complète de certaines informations, il est souvent nécessaire de regrouper des données provenant de plusieurs tables. > [!example] Exemple de schéma > Imaginons une base de données simple pour une entreprise : > - Table `Clients` : `id_client` (clé primaire), `nom_client`, `ville_client`. > - Table `Commandes` : `id_commande` (clé primaire), `id_client` (clé étrangère vers `Clients`), `date_commande`, `montant_total`. > > Pour savoir quels sont les clients qui ont passé des commandes et le détail de ces commandes, nous avons besoin de combiner ces deux tables. ## 2. Le Produit Cartésien Le produit cartésien est le moyen le plus simple, mais souvent le moins utile directement, de combiner des enregistrements de deux tables. Il sert de base conceptuelle aux autres types de jointures. > [!definition] Définition : Produit Cartésien > Le produit cartésien de deux tables $A$ et $B$ est une opération qui combine chaque enregistrement de la table $A$ avec chaque enregistrement de la table $B$. > Si la table $A$ contient $N_A$ enregistrements et la table $B$ contient $N_B$ enregistrements, le produit cartésien résultant contiendra $N_A \times N_B$ enregistrements. ### 2.1. Syntaxe SQL Il existe deux syntaxes principales pour effectuer un produit cartésien : 1. **Syntaxe implicite** ```sql SELECT * FROM TableA, TableB; ``` 2. **Syntaxe explicite** ```sql SELECT * FROM TableA CROSS JOIN TableB; ``` La syntaxe `CROSS JOIN` est plus explicite et généralement recommandée. >[!tip] Nous utiliserons la forme implicite >Afin de nous faciliter les choses, nous choisissons d'utiliser la forme implicite dans le cours et les exercices. >Nous vous laissons libre de choisir la forme qui vous convient le mieux. ### 2.2. Explication et Illustration Considérons nos tables `Clients` et `Commandes` : **Table `Clients`** | id_client | nom_client | ville_client | |-----------|------------|--------------| | 1 | Alice | Paris | | 2 | Bob | Lyon | **Table `Commandes`** | id_commande | id_client | date_commande | montant_total | |-------------|-----------|---------------|---------------| | 101 | 1 | 2023-01-15 | 150.00 | | 102 | 2 | 2023-01-16 | 200.00 | | 103 | 1 | 2023-01-17 | 50.00 | Si nous effectuons un produit cartésien : ```sql SELECT * FROM Clients, Commandes; ``` Le résultat serait : | id_client | nom_client | ville_client | id_commande | id_client | date_commande | montant_total | |-----------|------------|--------------|-------------|-----------|---------------|---------------| | 1 | Alice | Paris | 101 | 1 | 2023-01-15 | 150.00 | | 1 | Alice | Paris | 102 | 2 | 2023-01-16 | 200.00 | | 1 | Alice | Paris | 103 | 1 | 2023-01-17 | 50.00 | | 2 | Bob | Lyon | 101 | 1 | 2023-01-15 | 150.00 | | 2 | Bob | Lyon | 102 | 2 | 2023-01-16 | 200.00 | | 2 | Bob | Lyon | 103 | 1 | 2023-01-17 | 50.00 | > [!note] Utilité du Produit Cartésien > A notre niveau, le produit cartésien est assez rare. cependant, il a son intérêt quand on veut créer toutes les combinaisons possibles entre deux tables. > > Exemple : Si je veux connaitre le chiffre d'affaire de chaque famille de produits dans chaque région de France, il sera nécessaire d'utiliser un produit cartésien pour être certain d'avoir toutes les combinaisons et non pas seulement les combinaisons où j'ai fait des ventes. > [!warning] Que représente un enregistrement > Depuis le début, nous vous disons qu'il est important de savoir à quoi correspond un enregistrement présent à la sortie du `FROM`. > Avec un produit cartésien, ne correspond ni à un enregistrement de la table A, ni un enregistrement de la table B mais à une combinaison des deux. > > Dans l'exemple ci-dessus, un enregistrement, en sortie du `FROM`, correspond à une combinaison client/commande. ## 3. Les Jointures (JOIN) Les jointures sont des opérations SQL qui permettent de combiner des enregistrements de deux (ou plusieurs) tables en fonction de champs de relation communs entre elles. C'est le moyen standard et le plus efficace d'interroger des données réparties sur plusieurs tables. > [!definition] Définition : Jointure > Une jointure est une clause SQL qui combine des enregistrements de deux (ou plusieurs) tables basées sur un champ lié entre elles. La condition de jointure spécifie comment les enregistrements des tables doivent être appariées. ### 3.1. La Condition de Jointure (`ON`) La clause `ON` est essentielle pour toutes les jointures (sauf `CROSS JOIN`). Elle spécifie la condition qui doit être remplie pour que des enregistrements des deux tables soient combinés. >[!warning] Nous utiliserons uniquement les clés primaires et les clés étrangères >Les conditions peuvent se faire sur n'importe quel champ des deux tables avec une égalité ou une inégalité. >Généralement, cette condition compare, via une égalité, une clé primaire d'une table à une clé étrangère de l'autre table. > >Dans le niveau débutant et intermédiaire, nous nous limiterons à ce type de conditions. > ```sql SELECT C.nom_client, Co.date_commande FROM Clients C INNER JOIN Commandes Co ON C.id_client = Co.id_client; ``` > [!tip] Rappel : Alias de table > Il est fortement recommandé d'utiliser des alias de table (par exemple `Clients C` ou `clients AS C`) pour rendre les requêtes plus concises et plus lisibles surtout lorsqu'on utilise des champs de même nom (comme `id_client` dans notre exemple). > [!warning] Que représente un enregistrement > Lorsque que nous ferons des jointures entre deux tables, nous le ferons en utilisant comme condition l'égalité entre une clé primaire et sa clé étrangère. > Cela permet de dire que la table principale sera celle où se trouve la clé étrangère et donc qu'un enregistrement en sortie du `FROM` correspondra à cette table. > La table contenant la clé primaire permet de donner des informations complémentaires sur l'objet manipulé. > > Dans l'exemple ci-dessus, un enregistrement, en sortie du `FROM`, correspond à une commande. L'autre table permet de donner des informations sur le client qui passe la commande (son nom et la ville où il habite). ## 4. La Jointure Interne (INNER JOIN) La jointure interne est le type de jointure le plus courant. Elle retourne uniquement les enregistrement lorsque la condition de jointure est satisfaite dans **les deux tables**. C'est l'équivalent d'une intersection entre les ensembles de données des deux tables. > [!definition] Définition : INNER JOIN > Une `INNER JOIN` retourne uniquement les enregistrements où il y a une correspondance de valeurs dans les champs spécifiés par la condition de jointure dans *les deux* tables. Les enregistrements sans correspondance dans l'une ou l'autre table sont exclus du résultat. >[!tip] jointure symétrique >Pour les jointure `INNER JOIN`, nous parlerons aussi de **jointure symétrique**. ### 4.1. Syntaxe SQL ```sql SELECT champs_désirés FROM TableA INNER JOIN TableB ON TableA.champ_commun = TableB.champ_commun; ``` Le mot-clé `INNER` est optionnel ; `JOIN` seul est interprété comme `INNER JOIN` par défaut. Cependant, il est bonne pratique de l'inclure pour la clarté. ### 4.2. Explication et Illustration Reprenons nos tables `Clients` et `Commandes`. Nous voulons voir les noms des clients et les détails de leurs commandes. ```sql SELECT C.nom_client, C.ville_client, Co.id_commande, Co.date_commande, Co.montant_total FROM Clients C INNER JOIN Commandes Co ON C.id_client = Co.id_client; ``` **Table `Clients`** | id_client | nom_client | ville_client | | --------- | ---------- | ------------ | | 1 | Alice | Paris | | 2 | Bob | Lyon | | 3 | Charlie | Marseille | **Table `Commandes`** | id_commande | id_client | date_commande | montant_total | |-------------|-----------|---------------|---------------| | 101 | 1 | 2023-01-15 | 150.00 | | 102 | 2 | 2023-01-16 | 200.00 | | 103 | 1 | 2023-01-17 | 50.00 | | 104 | `NULL` | 2023-01-18 | 300.00 | Le résultat de l'`INNER JOIN` serait : | nom_client | ville_client | id_commande | date_commande | montant_total | |------------|--------------|-------------|---------------|---------------| | Alice | Paris | 101 | 2023-01-15 | 150.00 | | Alice | Paris | 103 | 2023-01-17 | 50.00 | | Bob | Lyon | 102 | 2023-01-16 | 200.00 | > [!note] Observations > - Le client `Charlie` (id_client = 3) n'apparaît pas car il n'a pas de commande correspondante dans la table `Commandes`. > - La commande `104` (id_client = `NULL`) n'apparaît pas car il n'y a pas de client correspondant dans la table `Clients`. > L'`INNER JOIN` est donc utile lorsque vous voulez uniquement les données qui ont une correspondance exacte dans *toutes* les tables jointes. > [!warning] Que représente un enregistrement > Nous avons déjà dit que la table principale sera celle où se trouve la clé étrangère et donc qu'un enregistrement en sortie du `FROM` correspondra à cette table. > > Le `INNER JOIN`, donne une information supplémentaire. En effet, il impose une égalité stricte entre la clé primaire et la clé étrangère : l'enregistrement sortant du `FROM` n'existera que s'il y a un enregistrement dans chaque table. > On en conclut qu'un enregistrement qui sort du `FROM` correspond à un enregistrement de la table contenant la clé étrangère dont on connait l'enregistrement de l'autre table (celle contenant la clé primaire). > > Dans l'exemple ci-dessus, un enregistrement, en sortie du `FROM`, correspond à **une commande dont on connait le client**. ## 5. Les Jointures Externes (OUTER JOINs) Contrairement à l'`INNER JOIN` qui ne retourne que les correspondances exactes, les jointures externes permettent d'inclure les enregistrements qui n'ont pas de correspondance dans l'une des tables. Il existe trois types de jointures externes mais nous nous concentrerons sur deux d'entre elles : `LEFT JOIN` et `RIGHT JOIN`. Cela permettra de bien comprendre le fonctionnement de ce type de jointure. > [!note] Valeurs `NULL` > Lorsqu'une jointure externe ne trouve pas de correspondance pour un enregistrement d'une table, les champs de la table non correspondante sont remplis avec des valeurs `NULL`. >[!tip] jointure dissymétrique > Pour les jointures `LEFT JOIN` et `RIGHT JOIN` nous parlerons aussi de **jointure dissymétrique** ### 5.1. La Jointure Gauche (LEFT JOIN / LEFT OUTER JOIN) > [!definition] Définition : LEFT JOIN > Une jointure `LEFT JOIN` retourne tous les enregistrements de la table de gauche, et les enregistrements correspondants de la table de droite. Si un enregistrement de la table de gauche n'a pas de correspondance dans la table de droite, les champs de la table de droite pour cet enregistrement seront `NULL`. ### 5.1.1. Syntaxe SQL ```sql SELECT champs_désirés FROM TableA LEFT JOIN TableB ON TableA.champ_commun = TableB.champ_commun; ``` Le mot-clé `OUTER` est optionnel. `LEFT JOIN` est suffisant. ### 5.1.2. Explication et Illustration Utilisons nos tables `Clients` et `Commandes` : ```sql SELECT C.nom_client, C.ville_client, Co.id_commande, Co.date_commande, Co.montant_total FROM Clients C LEFT JOIN Commandes Co ON C.id_client = Co.id_client; ``` **Table `Clients`** | id_client | nom_client | ville_client | |-----------|------------|--------------| | 1 | Alice | Paris | | 2 | Bob | Lyon | | 3 | Charlie | Marseille | **Table `Commandes`** | id_commande | id_client | date_commande | montant_total | |-------------|-----------|---------------|---------------| | 101 | 1 | 2023-01-15 | 150.00 | | 102 | 2 | 2023-01-16 | 200.00 | | 103 | 1 | 2023-01-17 | 50.00 | | 104 | 99 | 2023-01-18 | 300.00 | Le résultat de la `LEFT JOIN` serait : | nom_client | ville_client | id_commande | date_commande | montant_total | |------------|--------------|-------------|---------------|---------------| | Alice | Paris | 101 | 2023-01-15 | 150.00 | | Alice | Paris | 103 | 2023-01-17 | 50.00 | | Bob | Lyon | 102 | 2023-01-16 | 200.00 | | Charlie | Marseille | NULL | NULL | NULL | > [!note] Observations > - Tous les clients (`Alice`, `Bob`, `Charlie`) sont inclus dans le résultat, car `Clients` est la table de gauche. > - Pour `Charlie`, qui n'a pas de commande, les champs de la table `Commandes` sont `NULL`. > - La commande `104` (d'un client inconnu) n'apparaît pas, car elle n'a pas de correspondance dans la table de gauche (`Clients`). > [!tip] Cas d'utilisation courant (A connaitre !!!) > La `LEFT JOIN` est très utile pour trouver les enregistrements d'une table qui n'ont *pas* de correspondance dans une autre table. > Par exemple, pour trouver tous les clients qui n'ont jamais passé de commande : > ```sql > SELECT C.nom_client > FROM Clients C LEFT JOIN Commandes Co ON C.id_client = Co.id_client > WHERE Co.id_commande IS NULL; -- On filtre les enregistrements où la partie droite est NULL > ``` > Le résultat serait : > | nom_client | > |------------| > | Charlie | ### 5.2. La Jointure Droite (RIGHT JOIN / RIGHT OUTER JOIN) > [!definition] Définition : RIGHT JOIN > Une jointure `RIGHT JOIN` retourne tous les enregistrements de la table de droite, et les enregistrements correspondants de la table de gauche. Si un enregistrement de la table de droite n'a pas de correspondance dans la table de gauche, les champs de la table de gauche pour cet enregistrement seront `NULL`. ### 5.2.1. Syntaxe SQL ```sql SELECT champs_désirés FROM TableA RIGHT JOIN TableB ON TableA.champ_commun = TableB.champ_commun; ``` Le mot-clé `OUTER` est optionnel. `RIGHT JOIN` est suffisant. ### 5.2.2. Explication et Illustration Reprenons nos tables `Clients` et `Commandes` : ```sql SELECT C.nom_client, C.ville_client, Co.id_commande, Co.date_commande, Co.montant_total FROM Clients C RIGHT JOIN Commandes Co ON C.id_client = Co.id_client; ``` **Table `Clients`** | id_client | nom_client | ville_client | |-----------|------------|--------------| | 1 | Alice | Paris | | 2 | Bob | Lyon | | 3 | Charlie | Marseille | **Table `Commandes`** | id_commande | id_client | date_commande | montant_total | |-------------|-----------|---------------|---------------| | 101 | 1 | 2023-01-15 | 150.00 | | 102 | 2 | 2023-01-16 | 200.00 | | 103 | 1 | 2023-01-17 | 50.00 | | 104 | 99 | 2023-01-18 | 300.00 | Le résultat de la `RIGHT JOIN` serait : | nom_client | ville_client | id_commande | date_commande | montant_total | |------------|--------------|-------------|---------------|---------------| | Alice | Paris | 101 | 2023-01-15 | 150.00 | | Alice | Paris | 103 | 2023-01-17 | 50.00 | | Bob | Lyon | 102 | 2023-01-16 | 200.00 | | NULL | NULL | 104 | 2023-01-18 | 300.00 | > [!note] Observations > - Toutes les commandes (`101`, `102`, `103`, `104`) sont incluses dans le résultat, car `Commandes` est la table de droite. > - Pour la commande `104`, qui n'a pas de client correspondant, les champs de la table `Clients` sont `NULL`. > - Le client `Charlie` (sans commande) n'apparaît pas, car il n'a pas de correspondance dans la table de droite (`Commandes`). > [!tip] Équivalence `LEFT JOIN` / `RIGHT JOIN` > Toute jointure `RIGHT JOIN` peut être réécrite en jointure `LEFT JOIN` en inversant l'ordre des tables. Par exemple, la requête précédente est équivalente à : > ```sql > SELECT > C.nom_client, > C.ville_client, > Co.id_commande, > Co.date_commande, > Co.montant_total > FROM Commandes Co LEFT JOIN Clients C ON C.id_client = Co.id_client; > ``` > C'est pourquoi la `LEFT JOIN` est souvent plus utilisée que la `RIGHT JOIN`, car elle permet de toujours considérer la "table principale" comme étant à gauche, ce qui peut améliorer la lisibilité. > [!warning] Que représente un enregistrement > Nous avons déjà dit que la table principale sera celle où se trouve la clé étrangère et donc qu'un enregistrement en sortie du `FROM` correspondra à cette table. > > En fonction de la jointure externe vous pourrez être plus précis et donner une des affirmations suivantes : > > 1- Si votre jointure prend tous les enregistrements de la table où se trouve la clé étrangère, un enregistrement qui sort du `FROM` correspond exactement à un enregistrement de cette table > Exemple : > ```sql FROM Commandes Co LEFT JOIN Clients C ON C.id_client = Co.id_client >``` > Dans l'exemple ci-dessus, un enregistrement, en sortie du `FROM`, correspond à **une commande** > > 2- Si votre jointure prend tous les enregistrements de la table où se trouve la clé primaire, un enregistrement qui sort du `FROM` correspond à un enregistrement de cette table ou à l'absence d'enregistrement de cette table pour un enregistrement de l'autrre table > Exemple : > ```sql FROM Commandes Co RIGHT JOIN Clients C ON C.id_client = Co.id_client >``` > Dans l'exemple ci-dessus, un enregistrement, en sortie du `FROM`, correspond à **une commande dont on connait le client ou une absence de commande pour un client donnée.** Nous allons voir ensuite comment se préparer à répondre à une demande : [[05_Comment répondre à un demande]]