L’instruction SELECT est l’outil central en ABAP pour lire des données depuis les tables de base de données. Elle fait partie d’Open SQL, la variante SQL agnostique de base de données en ABAP qui fonctionne sur tous les systèmes de bases de données supportés par SAP.
Syntaxe
La syntaxe de base offre de nombreuses options :
1. Lire une seule ligne (SELECT SINGLE)
SELECT SINGLE <champs> FROM <table_base_donnees> WHERE <condition> INTO <cible>.2. Lire plusieurs lignes dans une table interne
SELECT <champs> FROM <table_base_donnees> [WHERE <condition>] [ORDER BY <champs>] INTO TABLE <table_interne>.3. Traitement ligne par ligne avec boucle
SELECT <champs> FROM <table_base_donnees> [WHERE <condition>] INTO <zone_travail>. " Traitement de chaque ligneENDSELECT.4. Syntaxe inline moderne (à partir d’ABAP 7.40)
SELECT <champs> FROM <table_base_donnees> WHERE <condition> INTO TABLE @DATA(lt_result).Composants
<champs>: Les colonnes à lire.*pour tous les champs ou une liste séparée par des virgules.FROM <table_base_donnees>: La table source (ou plusieurs pour les JOINs).WHERE <condition>: Condition de filtre pour les lignes.INTO <cible>: Variable cible, structure ou table.ORDER BY: Tri des résultats.GROUP BY: Regroupement pour les fonctions d’agrégation.HAVING: Filtre pour les résultats groupés.
Champs système
Après un SELECT, les champs système suivants sont définis :
-
sy-subrc:0: Au moins une ligne trouvée.4: Aucune ligne trouvée (pourSELECT SINGLEou ensemble de résultats vide).
-
sy-dbcnt: Nombre de lignes lues.
SELECT SINGLE vs. SELECT
| Variante | Résultat | Utilisation |
|---|---|---|
SELECT SINGLE | Maximum une ligne | Accès via clé complète |
SELECT ... INTO TABLE | Toutes les lignes correspondantes | Charger plusieurs enregistrements |
SELECT ... ENDSELECT | Traitement ligne par ligne | Legacy, à éviter si possible |
Recommandation : Évitez SELECT ... ENDSELECT - c’est inefficace. Chargez plutôt toutes les données dans une table interne et traitez-les avec LOOP AT.
Exemples
1. SELECT SINGLE - Lire une ligne
DATA: ls_customer TYPE kna1.
" Lire un client via clé primaireSELECT SINGLE * FROM kna1 WHERE kunnr = '0000001000" INTO @ls_customer.
IF sy-subrc = 0. WRITE: / 'Client trouvé :', ls_customer-name1.ELSE. WRITE: / 'Client non trouvé.'.ENDIF.2. Sélectionner des champs spécifiques
DATA: BEGIN OF ls_material, matnr TYPE matnr, maktx TYPE maktx, mtart TYPE mtart, END OF ls_material.
SELECT SINGLE matnr, maktx, mtart FROM mara INNER JOIN makt ON makt~matnr = mara~matnr WHERE mara~matnr = 'MAT001" AND makt~spras = @sy-langu INTO @ls_material.3. Lire plusieurs lignes dans une table
DATA: lt_orders TYPE TABLE OF vbak.
" Toutes les commandes d'un clientSELECT * FROM vbak WHERE kunnr = '0000001000" AND erdat >= '20250101" INTO TABLE @lt_orders.
WRITE: / 'Nombre de commandes :', sy-dbcnt.
LOOP AT lt_orders INTO DATA(ls_order). WRITE: / ls_order-vbeln, ls_order-erdat.ENDLOOP.4. Déclaration inline moderne
" La table est automatiquement créée avec le type appropriéSELECT matnr, maktx FROM makt WHERE spras = @sy-langu INTO TABLE @DATA(lt_materials).
" Valeur unique inlineSELECT SINGLE name1 FROM kna1 WHERE kunnr = '0000001000" INTO @DATA(lv_name).Conditions WHERE
La clause WHERE supporte diverses conditions :
Opérateurs de comparaison
SELECT * FROM vbak WHERE erdat = '20250101' " Égal AND netwr > 1000 " Supérieur AND auart <> 'ZOR' " Différent INTO TABLE @DATA(lt_orders).Opérateur IN (pour listes et Ranges)
" Avec liste de valeursSELECT * FROM mara WHERE mtart IN ('FERT', 'HALB', 'ROH') INTO TABLE @DATA(lt_materials).
" Avec table Range (ex. de SELECT-OPTIONS)DATA: lr_matnr TYPE RANGE OF matnr.lr_matnr = VALUE #( ( sign = 'I' option = 'BT' low = 'A' high = 'M' ) ).
SELECT * FROM mara WHERE matnr IN @lr_matnr INTO TABLE @lt_materials.BETWEEN pour les plages
SELECT * FROM vbak WHERE erdat BETWEEN '20250101' AND '20251231" INTO TABLE @DATA(lt_orders).LIKE pour la recherche de motifs
" % = n'importe quel nombre de caractères, _ = exactement un caractèreSELECT * FROM kna1 WHERE name1 LIKE 'Müller%" INTO TABLE @DATA(lt_customers).IS NULL / IS NOT NULL
SELECT * FROM but000 WHERE name_org1 IS NOT NULL INTO TABLE @DATA(lt_partners).JOINs - Relier des tables
INNER JOIN
Seulement les lignes qui existent dans les deux tables :
SELECT vbak~vbeln, vbak~erdat, vbap~matnr, vbap~kwmeng FROM vbak INNER JOIN vbap ON vbap~vbeln = vbak~vbeln WHERE vbak~kunnr = '0000001000" INTO TABLE @DATA(lt_order_items).LEFT OUTER JOIN
Toutes les lignes de la table de gauche, même sans correspondance à droite :
SELECT mara~matnr, makt~maktx FROM mara LEFT OUTER JOIN makt ON makt~matnr = mara~matnr AND makt~spras = @sy-langu INTO TABLE @DATA(lt_materials).JOINs multiples
SELECT vbak~vbeln, vbak~erdat, vbap~posnr, vbap~matnr, makt~maktx FROM vbak INNER JOIN vbap ON vbap~vbeln = vbak~vbeln LEFT OUTER JOIN makt ON makt~matnr = vbap~matnr AND makt~spras = @sy-langu WHERE vbak~erdat >= '20250101" INTO TABLE @DATA(lt_result).Fonctions d’agrégation
Fonctions disponibles
| Fonction | Description |
|---|---|
COUNT(*) | Nombre de lignes |
COUNT( DISTINCT champ ) | Nombre de valeurs distinctes |
SUM( champ ) | Somme |
AVG( champ ) | Moyenne |
MIN( champ ) | Minimum |
MAX( champ ) | Maximum |
Exemple : Agrégation simple
" Nombre de commandesSELECT COUNT(*) AS count FROM vbak WHERE kunnr = '0000001000" INTO @DATA(lv_count).
WRITE: / 'Nombre de commandes :', lv_count.
" Somme des valeurs de commandeSELECT SUM( netwr ) AS total FROM vbak WHERE kunnr = '0000001000" INTO @DATA(lv_total).GROUP BY - Agrégation groupée
" Chiffre d'affaires par clientSELECT kunnr, SUM( netwr ) AS total_amount FROM vbak WHERE erdat >= '20250101" GROUP BY kunnr INTO TABLE @DATA(lt_customer_totals).
LOOP AT lt_customer_totals INTO DATA(ls_total). WRITE: / 'Client :', ls_total-kunnr, 'CA :', ls_total-total_amount.ENDLOOP.HAVING - Filtre après regroupement
" Uniquement les clients avec CA > 10000SELECT kunnr, SUM( netwr ) AS total_amount FROM vbak WHERE erdat >= '20250101" GROUP BY kunnr HAVING SUM( netwr ) > 10000 INTO TABLE @DATA(lt_top_customers).ORDER BY - Tri
" Ascendant (par défaut)SELECT * FROM vbak WHERE kunnr = '0000001000" ORDER BY erdat ASCENDING INTO TABLE @DATA(lt_orders_asc).
" DescendantSELECT * FROM vbak WHERE kunnr = '0000001000" ORDER BY erdat DESCENDING INTO TABLE @DATA(lt_orders_desc).
" Plusieurs champs de triSELECT * FROM vbak ORDER BY kunnr ASCENDING, erdat DESCENDING INTO TABLE @DATA(lt_orders_multi).UP TO n ROWS - Limiter l’ensemble de résultats
" Seulement les 100 premières lignesSELECT * FROM vbak WHERE erdat >= '20250101" ORDER BY erdat DESCENDING UP TO 100 ROWS INTO TABLE @DATA(lt_recent_orders).DISTINCT - Supprimer les doublons
" Tous les types de commande distinctsSELECT DISTINCT auart FROM vbak INTO TABLE @DATA(lt_order_types).FOR ALL ENTRIES - Correspondance de masse avec table interne
DATA: lt_customers TYPE TABLE OF kna1.
" D'abord charger les clientsSELECT * FROM kna1 WHERE land1 = 'DE" INTO TABLE @lt_customers.
" Puis les commandes pour ces clientsIF lt_customers IS NOT INITIAL. SELECT * FROM vbak FOR ALL ENTRIES IN @lt_customers WHERE kunnr = @lt_customers-kunnr INTO TABLE @DATA(lt_orders).ENDIF.Important : Vérifiez toujours que la table pilote (lt_customers) n’est pas vide ! Avec une table vide, toutes les lignes de la table de base de données sont lues.
Sous-requêtes
" Clients qui ont des commandesSELECT * FROM kna1 WHERE kunnr IN ( SELECT kunnr FROM vbak WHERE erdat >= '20250101' ) INTO TABLE @DATA(lt_active_customers).
" Commandes au-dessus de la moyenneSELECT * FROM vbak WHERE netwr > ( SELECT AVG( netwr ) FROM vbak ) INTO TABLE @DATA(lt_above_avg).Caractère d’échappement (@) pour les variables hôtes
Dans l’ABAP Open SQL moderne, les variables ABAP doivent être marquées avec @ :
DATA: lv_customer TYPE kunnr VALUE '0000001000'.
" Moderne (recommandé)SELECT * FROM vbak WHERE kunnr = @lv_customer INTO TABLE @DATA(lt_orders).
" Legacy (sans @) - fonctionne encore, mais obsolèteSELECT * FROM vbak WHERE kunnr = lv_customer INTO TABLE lt_orders.Conseils de performance
-
Sélectionner uniquement les champs nécessaires :
" Mauvais : Tous les champsSELECT * FROM vbak INTO TABLE @DATA(lt_all)." Meilleur : Seulement les champs nécessairesSELECT vbeln, erdat, netwr FROM vbak INTO TABLE @DATA(lt_needed). -
Utiliser les index : Utilisez des conditions WHERE sur des champs indexés (généralement les champs clés).
-
Évitez SELECT … ENDSELECT :
" Mauvais : Nombreux accès base de donnéesSELECT * FROM vbak INTO @DATA(ls_order)." TraitementENDSELECT." Meilleur : Un seul accès base de donnéesSELECT * FROM vbak INTO TABLE @DATA(lt_orders).LOOP AT lt_orders INTO DATA(ls_order)." TraitementENDLOOP. -
Utiliser UP TO n ROWS : Si vous n’avez besoin que de quelques lignes, limitez l’ensemble de résultats.
-
FOR ALL ENTRIES avec précaution :
- Toujours vérifier si la table pilote est vide
- Supprimer les doublons dans la table pilote au préalable (avec
SORTetDELETE ADJACENT DUPLICATES)
-
Agrégation dans la base de données : Calculez les sommes, moyennes, etc. dans la base de données plutôt qu’en ABAP.
Notes importantes / Bonnes pratiques
- Vérifiez toujours
sy-subrcaprès unSELECT SINGLEou quand vous attendez une ligne. - Utilisez la syntaxe moderne avec
@pour les variables hôtes et@DATA()pour les déclarations inline. - Évitez
SELECT *si vous n’avez pas besoin de tous les champs. SELECT ... ENDSELECTest inefficace - chargez les données dans une table interne et utilisezLOOP AT.- Avec
FOR ALL ENTRIES, vérifiez toujoursIS NOT INITIALsur la table pilote. - Utilisez les JOINs plutôt que des SELECTs imbriqués pour de meilleures performances.
- Après le chargement, vous pouvez traiter les données avec
SORT,READ TABLEouMODIFY.