Analyse et test du code
Utilisation correcte des types de données et des conversions de types
Traitement des zones de caractères
Utilisation du Pushdown de code dans ABAP SQL
Amélioration de la performance des tables internes
Implémentation des contrôles des autorisations
Conception d'un code orienté objet efficace
Définition et utilisation des classes d'exception
Ajout de documentation au code ABAP

Implémentation de jointures

Objectives

After completing this lesson, you will be able to:
  • Implémenter des jointures.
  • Différencier les jointures internes des jointures externes.
  • Implémenter des jointures imbriquées.

Notions de base et syntaxe des jointures

Définition d'une jointure

Parfois, il est nécessaire de combiner deux sources de données SQL en un seul ensemble de résultats, constitué de colonnes des deux sources de données. C'est ce qu'on appelle une jointure.

Par exemple, lors de la lecture des liaisons aériennes, il peut être nécessaire de lire également les détails sur le transporteur aérien.

Les lignes de l'ensemble de résultats sont déterminées par le type de jointure et par les conditions de jointure entre les colonnes des sources de données. Les conditions de jointure commencent par le mot-clé ON et définissent les relations entre les colonnes des sources de données.

Le type de jointure est défini par le mot-clé correspondant. Dans ABAP SQL, les types de jointure pris en charge sont INNER JOIN, LEFT OUTER JOIN et RIGHT OUTER JOIN.

Nous aborderons bientôt les différents résultats de ces types de jointure. Examinons d'abord la syntaxe ABAP SQL des jointures.

L'exemple combine les données du transporteur (ID du transporteur et nom du transporteur) et les données de connexion (ID de connexion, aéroport de départ et aéroport de destination) dans un ensemble de résultats. La source de données de gauche est la table de base de données /DMO/CARRIER alors que la table de base de données /DMO/CONNECTION sert de source de données de droite. Le mot-clé INNER JOIN entre les deux noms de source de données indique le type de jointure.

La condition de jointure après ON demande que le contenu de la zone CARRIER_ID de la table de base de données /DMO/CARRIER soit égal au contenu de la zone CARRIER_ID de la table de base de données /DMO/CONNECTION.

Dans les conditions de jointure ainsi que dans la liste de zones, la clause WHERE, etc., les colonnes des sources de données sont entièrement identifiées par le nom de la source de données et le nom de la colonne, séparés par le signe tilde ( ~ ).

Remarque

À partir d'autres dialectes SQL, vous pouvez être utilisé pour le point ou le signe d'arrêt complet ( . ). Cela devait être remplacé par un autre signe dans ABAP SQL, car le signe de période a déjà la signification de terminer l'instruction.

Il est possible d'adresser les noms de colonnes sans le nom de la source de données. La condition préalable est que le nom de la colonne existe uniquement dans l'une des sources de données.

Dans l'exemple, seule la table de base de données /DMO/CARRIER contient les colonnes NAME et CURRENCY_CODE, tandis que les colonnes CONNECTION_ID, AIRPORT_FROM_ID et AIRPORT_TO_ID existent uniquement dans la table de base de données /DMO/CONNECTION. Par conséquent, ces noms de colonnes sont uniques sans prédécesseur/DMO/CARRIER~ ou /DMO/CONNECTION~.

D'autre part, les deux tables contiennent une colonne CARRIER_ID. Par conséquent, pour cette zone /DMO/CARRIER~ ou /DMO/CONNECTION~ est obligatoire.

Il est recommandé d'utiliser des alias pour les noms de sources de données afin de raccourcir l'instruction et d'améliorer la lisibilité. Pour ce faire, utilisez l'option AS après le nom de la source de données. Dans l'exemple, l'alias A est utilisé pour la table de base de données /DMO/CARRIER et l'alias C pour la table de base de données /DMO/CONNECTION.

Si une source de données a un alias, il est obligatoire d'utiliser l'alias à la place du nom de la source de données d'origine.

Alias requis dans les auto-jointures

Dans certains cas, les alias des sources de données sont plus qu'une simple alternative syntaxique. Elles deviennent indispensables dans les instructions qui contiennent plusieurs fois la même source de données.

Prenons un exemple. Considérons que nous avons une table avec des liaisons aériennes (/DMO/CONNECTION). Nous sommes intéressés par les destinations que nous pouvons atteindre à partir de l'aéroport international de Francfort (FRA). Mais au lieu de vols directs, nous recherchons des vols avec un seul transfert.

La solution est une jointure de la table /DMO/CONNECTION avec elle-même. La première condition d'adhésion demande que la deuxième liaison (2e étape) commence à partir de l'aéroport où la première liaison (première étape) se termine. La deuxième condition de jointure, liée à la première avec l'opérateur AND, exclut les combinaisons où la deuxième liaison est un vol retour direct vers l'aéroport de départ d'origine.

Il est évident qu'une telle auto-jointure ne fonctionnerait pas sans les noms d'alias a pour l'utilisation gauche de /DMO/CONNECTION et b pour l'utilisation correcte de /DMO/CONNECTION. Sans les différents alias, il ne serait pas possible de faire la distinction entre la première connexion et la deuxième connexion - ni dans la condition de jointure, ni dans la liste de zones ou dans la clause WHERE.

Remarque

Dans ce type d'auto-jointure, le nom d'alias est requis devant chaque nom de colonne car chaque nom de colonne existe dans les deux sources de données.

Essayer : la syntaxe Join

  1. Créez une nouvelle classe globale qui implémente l'interface IF_OO_ADT_CLASSRUN.
  2. Copiez l'extrait de code suivant dans la partie implémentation de la méthode if_oo_adt_classrun~main( ) :
    Code Snippet
    1234567891011121314151617181920
    SELECT FROM /dmo/carrier INNER JOIN /dmo/connection * SELECT FROM /dmo/carrier AS a INNER JOIN /dmo/connection AS c ON /dmo/carrier~carrier_id = /dmo/connection~carrier_id FIELDS /dmo/carrier~carrier_id, /dmo/carrier~name AS carrier_name, /dmo/connection~connection_id, /dmo/connection~airport_from_id, /dmo/connection~airport_to_id WHERE /dmo/carrier~currency_code = 'EUR' INTO TABLE @DATA(result). out->write( EXPORTING data = result name = 'RESULT' ).
  3. Sélectionnez CTRL + F3 pour activer la classe et F9 pour l'exécuter en tant qu'application de console.
  4. Analysez la sortie de la console.
  5. Placez le curseur sur la première ligne (la ligne avec le mot-clé SELECT) et appuyez SUR CTRL + < pour ajouter un signe étoile (*) dans la première colonne de cette ligne.
  6. Supprimez le signe étoile (*) au début de la ligne suivante. Analysez l'erreur de syntaxe qui indique que le nom de table /DMO/CARRIER est masqué par un nom d'alias.
  7. Dans la condition ON, remplacez le nom de table /DMO/CARRIER par son alias A et le nom de table /DMO/CONNECTION par son nom d'alias C.
  8. Dans la liste FIELDS et la clause WHERE, supprimez le nom d'alias et le tilde (~) devant chaque nom de zone lorsque cela est possible. Vous devez constater qu'elle est obligatoire uniquement devant la zone carrier_id.
  9. Activez et exécutez à nouveau.

Types de jointures

Regardez les vidéos suivantes pour en savoir plus sur la jointure interne, la jointure externe droite et la jointure externe gauche.

Cet exemple illustre le résultat d'une jointure externe gauche. Les deux lignes mises en surbrillance dans l'ensemble de résultats ne seraient pas présentes dans une jointure interne. Ils correspondent à des transporteurs qui n'ont pas de liaisons aériennes dans la table de base de données /DMO/CONNECTION.

Ces lignes comprennent uniquement des informations sur le transporteur, c'est-à-dire des informations de la source de données de gauche, table /DMO/CARRIER. Les colonnes qui contiennent généralement des informations de la source de données appropriée, c'est-à-dire les colonnes CONNECTION_ID, AIRPORT_FROM_ID et AIRPORT_TO_ID, sont renseignées avec des valeurs initiales. Ces valeurs ont été saisies par l'exécution ABAP au lieu des valeurs nulles retournées par la base de données.

Remarque

Le type de colonne CONNECTION_ID est basé sur le type prédéfini ABAP N. La valeur initiale pour ce type n'est pas une série d'espaces, mais une série de zéros.

Essayer : jointure interne et jointures externes

  1. Créez une nouvelle classe globale qui implémente l'interface IF_OO_ADT_CLASSRUN.
  2. Copiez l'extrait de code suivant dans la partie implémentation de la méthode if_oo_adt_classrun~main( ) :
    Code Snippet
    1234567891011121314151617181920212223242526
    SELECT FROM /dmo/Agency AS a INNER JOIN /dmo/customer AS c * LEFT OUTER JOIN /dmo/customer AS c * RIGHT OUTER JOIN /dmo/customer AS c ON a~city = c~city FIELDS agency_id, name AS Agency_name, a~city AS agency_city, c~city AS customer_city, customer_id, last_name AS customer_name WHERE ( c~customer_id < '000010' OR c~customer_id IS NULL ) AND ( a~agency_id < '070010' OR a~agency_id IS NULL ) INTO TABLE @DATA(result_Join). out->write( EXPORTING data = result_join name = 'RESULT_JOIN' ).
  3. Sélectionnez CTRL + F3 pour activer la classe et F9 pour l'exécuter en tant qu'application de console.
  4. Analysez la sortie de la console.

    Remarque

    L'exemple de code récupère des paires de clients et d'agences de voyages avec une adresse dans la même ville. Elle utilise une clause WHERE pour limiter l'ensemble de résultats. Ainsi, avec le INNER JOIN, seuls le client "000010" et l'agence de voyages "070010" sont trouvés. La clause WHERE est écrite sous une forme qui fonctionne également pour les jointures externes. Les conditions <field_name> IS NULL sont nécessaires pour ne pas exclure les résultats avec des valeurs NULL de la base de données.
  5. Dans la clause FROM de l'instruction SELECT, remplacez INNER JOIN par LEFT OUTER JOIN et activez à nouveau. Vous pouvez également commenter la ligne avec INNER JOIN et supprimer les commentaires de la ligne avec LEFT OUTER JOIN.
  6. Exécutez à nouveau et comparez la sortie de la console à la première sortie.
  7. Dans la clause FROM de l'instruction SELECT, remplacez LEFT OUTER JOIN par RIGHT OUTER JOIN et activez à nouveau. Vous pouvez également commenter la ligne avec LEFT OUTER JOIN et supprimer les commentaires de la ligne avec RIGHT OUTER JOIN.
  8. Exécutez à nouveau et comparez la sortie de la console aux deux premières sorties.

Jointures imbriquées

Jointure de plusieurs sources de données

Dans ABAP SQL, vous pouvez joindre plusieurs sources de données en même temps. Commençons par la jointure de deux sources de données, à savoir les compagnies aériennes et les liaisons aériennes dont nous avons parlé précédemment.

Supposons maintenant que nous voulions ajouter la table de base de données /DMO/AIRPORT pour lire le nom et la ville de l'aéroport de départ (AIRPORT_FROM_ID).

Pour joindre les trois sources de données, nous entourons d'abord la jointure existante avec une paire de crochets. Cette première jointure sert maintenant de source de données de gauche pour la jointure suivante. Nous ajoutons le mot-clé de jointure (INNER JOIN, LEFT OUTER JOIN, RIGHT OUTER JOIN), la bonne source de données (/DMO/AIRPORT), un alias (AS apf) et la condition de jointure pour la jointure supplémentaire (ON c~airport_from_id = apf~airport_id).

Supposons maintenant que nous voulions ajouter à nouveau la table de base de données /DMO/AIRPORT pour lire également le nom et la ville de l'aéroport de destination (AIRPORT_TO_ID).

Pour implémenter cette jointure de quatre sources de données, nous répétons ce que nous avons fait pour la jointure de trois sources de données : nous entourons la jointure existante de trois sources de données avec une autre paire de crochets et ajoutons le mot-clé de jointure, la source de données droite et la condition de jointure.

Remarque

Vous pouvez répéter cette opération jusqu'à ce que vous atteigniez le nombre maximal de sources de données pouvant être liées les unes aux autres. Actuellement, jusqu'à 50 sources de données (49 jointures) sont autorisées dans une instruction SELECT.

Séquence de jointures

Dans l'exemple précédent, la source de données suivante a toujours été ajoutée à droite de la jointure suivante. La source de données de gauche était une table de base de données ou une jointure. La source de données correcte des trois jointures était une table de base de données.

Grâce aux crochets, il est également possible d'utiliser une jointure comme source de données droite d'une autre jointure. Dans l'exemple inférieur, nous commençons par une jointure de /DMO/CONNECTION et /DMO/AIRPORT (AS apf). Nous l'entourons de crochets et l'utilisons comme source de données gauche d'une jointure avec /DMO/AIRPORT (AS apt). Pour ajouter /DMO/CARRIER, nous entourons la jointure existante avec une autre paire de crochets. Ensuite, nous ajoutons la source de données gauche (/DMO/CARRIER) et le mot-clé de jointure avant la parenthèse ouvrante et la condition ON après la parenthèse fermante.

Remarque

Dans notre exemple, les deux résultats sont identiques. Cependant, dans certaines situations, le résultat peut dépendre de la séquence dans laquelle la base de données évalue les jointures.

Importance des parenthèses

D'un point de vue syntaxique, les parenthèses ne sont pas nécessaires. Mais pour des raisons de lisibilité, il est fortement recommandé de ne pas omettre les parenthèses dans les jointures imbriquées. Cela doit être illustré par l'exemple suivant.

L'instruction SELECT contient une jointure de quatre tables de base de données mais n'utilise pas de parenthèses. En un coup d'œil superficiel, on pourrait penser que la jointure de /DMO/CARRIER et /DMO/CONNECTION est évaluée en premier car ces tables sont les premières à apparaître dans la clause FROM.

Mais cette conclusion est fausse !

Sans parenthèses, la séquence d'évaluation est déterminée par la séquence des conditions ON et non par la séquence des sources de données. Lors d'une inspection plus approfondie, nous constatons que la condition ON qui relie les tables /DMO/CARRIER (alias a) et /DMO/CONNECTION (alias c) entre elles est placée à la fin de la clause FROM. Par conséquent, cette jointure est la dernière à être évaluée.

Astuce

Utilisez toujours des parenthèses dans les jointures imbriquées pour éviter ce type de confusion.

Implémenter une jointure imbriquée

Dans cet exercice, vous implémentez une jointure imbriquée pour lire les liaisons aériennes et les détails de l'aéroport dans la même instruction SELECT.

Modèle :

  • /LRN/CL_S4D401_TCS_STRING_PROC (classe globale)

Solution :

  • /LRN/CL_S4D401_DBS_NESTED_JOIN (classe globale)

Tâche 1: Copier modèle (facultatif)

Copiez la classe de modèle /LRN/CL_S4D401_TCS_STRING_PROC. Si vous avez terminé l'exercice précédent, vous pouvez ignorer cette tâche et continuer à modifier votre classe ZCL_##_SOLUTION.

Étapes

  1. Copiez la classe /LRN/CL_S4D401_TCS_STRING_PROC dans une classe de votre propre package (nom proposé : ZCL_##_SOLUTION, ## correspondant à votre numéro de groupe).

    1. Dans l'explorateur de projets, cliquez avec le bouton droit de la souris sur la classe /LRN/CL_S4D401_TCS_STRING_PROC pour ouvrir le menu contextuel.

    2. Dans le menu contextuel, sélectionnez Dupliquer....

    3. Saisissez le nom de votre package dans la zone Package. Dans la zone Nom, saisissez ZCL_##_SOLUTION, où ## représente votre numéro de groupe.

    4. Cliquez sur Next.

    5. Confirmez l'ordre de transport et cliquez sur Terminer.

  2. Activez la copie.

    1. Appuyez sur Ctrl + F3 pour activer la classe.

Tâche 2: Implémenter la jointure

Adaptez l'implémentation de la méthode CLASS_CONSTRUCTOR de la classe locale LCL_PASSENGER_FLIGHT. Étendez l'instruction SELECT qui lit à partir de la table de base de données /LRN/CONNECTION. Ajoutez deux jointures pour lire les fuseaux horaires de l'aéroport de départ et de l'aéroport de destination.

Étapes

  1. Naviguez vers l'implémentation de la méthode CLASS_CONSTRUCTOR dans la classe locale LCL_PASSENGER_FLIGHT.

    1. Par exemple, vous pouvez ouvrir l'onglet Types locaux dans l'éditeur et rechercher METHOD class_constructor.

    2. Vous pouvez également développer ZCL_##_SOLUTIONLCL_PASSENGER_FLIGHT dans la vue Structure à gauche et sélectionner CLASS_CONSTRUCTOR.

  2. Recherchez l'instruction SELECT qui lit à partir de la table de base de données /LRN/CONNECTION et ajoutez un alias pour la source de données (nom d'alias suggéré : c).

    1. Adaptez le code comme suit :

      ABAP
      12345
      SELECT FROM /lrn/connection AS c FIELDS carrier_id, connection_id, airport_from_id, airport_to_id, departure_time, arrival_time INTO TABLE @connections_buffer.
  3. Ajoutez une jointure avec un type JOIN approprié qui lit les détails de l'aéroport de départ à partir de la table de base de données /LRN/AIRPORT. Ajoutez un alias pour la deuxième source de données (nom d'alias suggéré : f).

    1. Adaptez le code comme suit :

      ABAP
      123456
      SELECT FROM lrn/connection AS c LEFT OUTER JOIN /lrn/airport AS f FIELDS carrier_id, connection_id, airport_from_id, airport_to_id, departure_time, arrival_time INTO TABLE @connections_buffer.
  4. Ajoutez la condition de jointure.

    1. Adaptez le code comme suit :

      ABAP
      1234567
      SELECT FROM /lrn/connection AS c LEFT OUTER JOIN /lrn/airport AS f ON c~airport_from_id = f~airport_id FIELDS carrier_id, connection_id, airport_from_id, airport_to_id, departure_time, arrival_time INTO TABLE @connections_buffer.
  5. Ajoutez une jointure similaire qui lit les détails de l'aéroport de destination et ajoutez un alias différent pour la troisième source de données (nom d'alias suggéré : t).

    1. Adaptez le code comme suit :

      ABAP
      123456789
      SELECT FROM /lrn/connection AS c LEFT OUTER JOIN /lrn/airport AS f ON c~airport_from_id = f~airport_id LEFT OUTER JOIN /lrn/airport AS t ON c~airport_to_id = t~airport_id FIELDS carrier_id, connection_id, airport_from_id, airport_to_id, departure_time, arrival_time INTO TABLE @connections_buffer.
  6. Ajoutez le fuseau horaire de l'aéroport de départ et le fuseau horaire de l'aéroport de destination à la liste de zones. Fournissez des alias appropriés (noms d'alias suggérés : timzone_from et timzone_to).

    1. Adaptez le code comme suit :

      ABAP
      1234567891011
      SELECT FROM /lrn/connection AS c LEFT OUTER JOIN /lrn/airport AS f ON c~airport_from_id = f~airport_id LEFT OUTER JOIN /lrn/airport AS t ON c~airport_to_id = t~airport_id FIELDS carrier_id, connection_id, airport_from_id, airport_to_id, departure_time, arrival_time, f~timzone AS timzone_from, t~timzone AS timzone_to INTO TABLE @connections_buffer.
  7. Accédez au type de ligne de la table interne connections_buffer et ajoutez deux composants pour les fuseaux horaires. Utilisez les mêmes noms que ceux que vous avez utilisés comme noms d'alias dans la liste de zones de l'instruction SELECT.

    Attention

    Assurez-vous d'ajouter les nouveaux composants avant la durée du composant. Cela est important car l'élément de durée n'est pas renseigné dans l'instruction SELECT. Vous pouvez également utiliser l'option CORRESPONDING FIELDS OF dans la clause INTO de l'instruction SELECT.
    1. Recherchez la définition du type de structure st_connections_buffer.

    2. Adaptez le code comme suit :

      ABAP
      123456789101112
      TYPES: BEGIN OF st_connections_buffer, carrier_id TYPE /dmo/carrier_id, connection_id TYPE /dmo/connection_id, airport_from_id TYPE /dmo/airport_from_id, airport_to_id TYPE /dmo/airport_to_id, departure_time TYPE /dmo/flight_departure_time, arrival_time TYPE /dmo/flight_departure_time, timzone_from TYPE timezone, timzone_to TYPE timezone, duration TYPE i, END OF st_connections_buffer.

Tâche 3: Supprimer SELECT redondant

Dans l'implémentation de la méthode CLASS_CONSTRUCTOR de la classe locale LCL_PASSENGER_FLIGHT, utilisez les informations de fuseau horaire de la table interne connections_buffer dans le calcul de la durée du vol. Supprimez ensuite l'accès en lecture redondant à la table de base de données /LRN/AIRPORT.

Étapes

  1. Revenez à l'implémentation de la méthode CLASS_CONSTRUCTOR dans la classe locale LCL_PASSENGER_FLIGHT.

    1. Pour cette étape, procédez comme vous l'avez fait précédemment.

  2. Dans l'option TIME ZONE de la première instruction CONVERT, remplacez l'accès en lecture aux aéroports des tables internes locales par un accès au composant timzone_from de la connexion de structure.

    1. Ajustez la première instruction CONVERT comme suit :

      ABAP
      12345
      CONVERT DATE today TIME connection-departure_time * TIME ZONE airports[ airport_id = connection-airport_from_id ]-timzone TIME ZONE connection-timzone_from INTO UTCLONG DATA(departure_utclong).
  3. Ajustez la deuxième instruction CONVERT de la même manière, en utilisant le composant timzone_to this time.

    1. Ajustez la deuxième instruction CONVERT comme suit :

      ABAP
      12345
      CONVERT DATE today TIME connection-arrival_time * TIME ZONE airports[ airport_id = connection-airport_to_id ]-timzone TIME ZONE connection-timzone_to INTO UTCLONG DATA(arrival_utclong).
  4. Faites défiler vers le haut et ajoutez des signes de commentaire devant toutes les lignes de code de l'instruction SELECT qui lit à partir de la table de base de données /LRN/AIRPORT.

    1. Faites défiler jusqu'au début de l'implémentation de la méthode CLASS_CONSTRUCTOR.

    2. Sélectionnez l'instruction SELECT complète qui lit la table de base de données /LRN/AIRPORT et appuyez sur Ctrl + <.

    3. Le code doit alors ressembler à ceci :

      ABAP
      1234
      * SELECT * FROM /lrn/airport * FIELDS airport_id, timzone * INTO TABLE @DATA(airports).
  5. Activez et testez votre classe globale comme application de console.

    1. Appuyez sur Ctrl + F3.

    2. Appuyez sur F9.