Instruction ABAP SELECT : Lire des données depuis les tables de base de données

Catégorie
ABAP-Statements
Publié
Auteur
Johannes

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 ligne
ENDSELECT.

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 (pour SELECT SINGLE ou ensemble de résultats vide).
  • sy-dbcnt : Nombre de lignes lues.

SELECT SINGLE vs. SELECT

VarianteRésultatUtilisation
SELECT SINGLEMaximum une ligneAccès via clé complète
SELECT ... INTO TABLEToutes les lignes correspondantesCharger plusieurs enregistrements
SELECT ... ENDSELECTTraitement ligne par ligneLegacy, à é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é primaire
SELECT 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 client
SELECT *
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 inline
SELECT 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 valeurs
SELECT * 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ère
SELECT * 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

FonctionDescription
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 commandes
SELECT COUNT(*) AS count
FROM vbak
WHERE kunnr = '0000001000"
INTO @DATA(lv_count).
WRITE: / 'Nombre de commandes :', lv_count.
" Somme des valeurs de commande
SELECT SUM( netwr ) AS total
FROM vbak
WHERE kunnr = '0000001000"
INTO @DATA(lv_total).

GROUP BY - Agrégation groupée

" Chiffre d'affaires par client
SELECT 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 > 10000
SELECT 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).
" Descendant
SELECT * FROM vbak
WHERE kunnr = '0000001000"
ORDER BY erdat DESCENDING
INTO TABLE @DATA(lt_orders_desc).
" Plusieurs champs de tri
SELECT * 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 lignes
SELECT * 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 distincts
SELECT 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 clients
SELECT * FROM kna1
WHERE land1 = 'DE"
INTO TABLE @lt_customers.
" Puis les commandes pour ces clients
IF 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 commandes
SELECT * FROM kna1
WHERE kunnr IN ( SELECT kunnr FROM vbak WHERE erdat >= '20250101' )
INTO TABLE @DATA(lt_active_customers).
" Commandes au-dessus de la moyenne
SELECT * 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ète
SELECT * FROM vbak
WHERE kunnr = lv_customer
INTO TABLE lt_orders.

Conseils de performance

  1. Sélectionner uniquement les champs nécessaires :

    " Mauvais : Tous les champs
    SELECT * FROM vbak INTO TABLE @DATA(lt_all).
    " Meilleur : Seulement les champs nécessaires
    SELECT vbeln, erdat, netwr FROM vbak INTO TABLE @DATA(lt_needed).
  2. Utiliser les index : Utilisez des conditions WHERE sur des champs indexés (généralement les champs clés).

  3. Évitez SELECT … ENDSELECT :

    " Mauvais : Nombreux accès base de données
    SELECT * FROM vbak INTO @DATA(ls_order).
    " Traitement
    ENDSELECT.
    " Meilleur : Un seul accès base de données
    SELECT * FROM vbak INTO TABLE @DATA(lt_orders).
    LOOP AT lt_orders INTO DATA(ls_order).
    " Traitement
    ENDLOOP.
  4. Utiliser UP TO n ROWS : Si vous n’avez besoin que de quelques lignes, limitez l’ensemble de résultats.

  5. 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 SORT et DELETE ADJACENT DUPLICATES)
  6. 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-subrc après un SELECT SINGLE ou 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 ... ENDSELECT est inefficace - chargez les données dans une table interne et utilisez LOOP AT.
  • Avec FOR ALL ENTRIES, vérifiez toujours IS NOT INITIAL sur 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 TABLE ou MODIFY.