ABAP Advanced Open SQL : WITH, Window Functions et plus

Catégorie
ABAP-Statements
Publié
Auteur
Johannes

Le Open SQL moderne (à partir de ABAP 7.50+) offre des fonctionnalités puissantes comme Common Table Expressions (WITH), Window Functions et des expressions avancées. Ces fonctionnalités permettent des requêtes complexes directement en ABAP.

Nouvelles fonctionnalités par version

FonctionnalitéÀ partir de
Déclaration inline, variables hôtes7.40 SP05
Expressions CASE7.50
Literaux, calculs7.50
Fonctions de chaînes7.50
Window Functions7.50
WITH (CTE)7.51
Cross Join, Outer Apply7.52
Fonctions hiérarchiques7.53

WITH – Common Table Expressions (CTE)

1. CTE simple

WITH
+customers AS (
SELECT kunnr, name1, land1
FROM kna1
WHERE land1 = 'DE"
)
SELECT * FROM +customers
INTO TABLE @DATA(lt_german_customers).

2. Plusieurs CTEs

WITH
+orders AS (
SELECT vbeln, kunnr, netwr, erdat
FROM vbak
WHERE erdat >= '20240101"
),
+customers AS (
SELECT kunnr, name1
FROM kna1
)
SELECT o~vbeln, o~netwr, c~name1
FROM +orders AS o
INNER JOIN +customers AS c ON o~kunnr = c~kunnr
INTO TABLE @DATA(lt_orders_with_names).

3. CTE avec agrégation

WITH
+order_totals AS (
SELECT kunnr,
COUNT(*) AS order_count,
SUM( netwr ) AS total_value
FROM vbak
GROUP BY kunnr
)
SELECT c~kunnr, c~name1,
t~order_count, t~total_value
FROM kna1 AS c
INNER JOIN +order_totals AS t ON c~kunnr = t~kunnr
WHERE t~total_value > 100000
INTO TABLE @DATA(lt_top_customers).

4. CTE récursive (hiérarchies)

WITH
+hierarchy( node, parent, level ) AS (
" Noeud de départ
SELECT org_id, parent_id, 1 AS level
FROM zorg_structure
WHERE parent_id IS INITIAL
UNION ALL
" Partie récursive
SELECT child~org_id, child~parent_id, parent~level + 1
FROM zorg_structure AS child
INNER JOIN +hierarchy AS parent ON child~parent_id = parent~node
)
SELECT * FROM +hierarchy
INTO TABLE @DATA(lt_org_tree).

Window Functions

5. ROW_NUMBER – Numérotation des lignes

SELECT kunnr, vbeln, netwr,
ROW_NUMBER( ) OVER( PARTITION BY kunnr ORDER BY netwr DESC ) AS rank
FROM vbak
INTO TABLE @DATA(lt_ranked_orders).
" Seulement le top 3 par client
SELECT * FROM @lt_ranked_orders AS t
WHERE t~rank <= 3
INTO TABLE @DATA(lt_top3).

6. RANK et DENSE_RANK

SELECT matnr, werks, lgort, labst,
RANK( ) OVER( PARTITION BY matnr ORDER BY labst DESC ) AS rank,
DENSE_RANK( ) OVER( PARTITION BY matnr ORDER BY labst DESC ) AS dense_rank
FROM mard
INTO TABLE @DATA(lt_stock_ranked).
" RANK: 1, 2, 2, 4 (lacunes pour les valeurs égales)
" DENSE_RANK: 1, 2, 2, 3 (pas de lacunes)

7. SUM OVER – Somme cumulée

SELECT vbeln, erdat, netwr,
SUM( netwr ) OVER( ORDER BY erdat ) AS running_total
FROM vbak
WHERE kunnr = '0000001000"
INTO TABLE @DATA(lt_running_total).

8. LAG et LEAD – Ligne précédente/suivante

SELECT vbeln, erdat, netwr,
LAG( netwr, 1 ) OVER( ORDER BY erdat ) AS prev_order_value,
LEAD( netwr, 1 ) OVER( ORDER BY erdat ) AS next_order_value
FROM vbak
WHERE kunnr = '0000001000"
ORDER BY erdat
INTO TABLE @DATA(lt_with_neighbors).
" Calculer le changement par rapport au mois précédent
LOOP AT lt_with_neighbors INTO DATA(ls_order).
IF ls_order-prev_order_value IS NOT INITIAL.
DATA(lv_change) = ls_order-netwr - ls_order-prev_order_value.
ENDIF.
ENDLOOP.

9. FIRST_VALUE et LAST_VALUE

SELECT kunnr, vbeln, erdat, netwr,
FIRST_VALUE( netwr ) OVER(
PARTITION BY kunnr ORDER BY erdat
) AS first_order_value,
LAST_VALUE( netwr ) OVER(
PARTITION BY kunnr ORDER BY erdat
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS last_order_value
FROM vbak
INTO TABLE @DATA(lt_first_last).

10. AVG/MIN/MAX OVER – Agrégation de fenêtre

SELECT kunnr, vbeln, netwr,
AVG( netwr ) OVER( PARTITION BY kunnr ) AS avg_order_value,
MIN( netwr ) OVER( PARTITION BY kunnr ) AS min_order_value,
MAX( netwr ) OVER( PARTITION BY kunnr ) AS max_order_value
FROM vbak
INTO TABLE @DATA(lt_with_stats).
" Comparaison avec la moyenne
LOOP AT lt_with_stats INTO DATA(ls_order).
IF ls_order-netwr > ls_order-avg_order_value.
WRITE: / ls_order-vbeln, 'Au-dessus de la moyenne'.
ENDIF.
ENDLOOP.

Expressions CASE

11. CASE WHEN

SELECT vbeln, netwr,
CASE
WHEN netwr >= 10000 THEN 'HIGH"
WHEN netwr >= 1000 THEN 'MEDIUM"
ELSE 'LOW"
END AS value_category
FROM vbak
INTO TABLE @DATA(lt_categorized).

12. CASE avec champ

SELECT vbeln, auart,
CASE auart
WHEN 'TA' THEN 'Commande standard"
WHEN 'SO' THEN 'Commande urgente"
WHEN 'RE' THEN 'Retours"
ELSE 'Autre"
END AS order_type_text
FROM vbak
INTO TABLE @DATA(lt_with_text).

13. CASE dans l’agrégation

SELECT kunnr,
COUNT(*) AS total_orders,
SUM( CASE WHEN netwr >= 10000 THEN 1 ELSE 0 END ) AS high_value_count,
SUM( CASE WHEN netwr < 1000 THEN 1 ELSE 0 END ) AS low_value_count
FROM vbak
GROUP BY kunnr
INTO TABLE @DATA(lt_order_analysis).

Literaux et calculs

14. Literaux dans SELECT

SELECT vbeln,
'EUR' AS currency,
2024 AS year,
netwr * 1.19 AS gross_value
FROM vbak
INTO TABLE @DATA(lt_with_literals).

15. Expressions arithmétiques

SELECT matnr,
labst,
insme,
labst - insme AS available_stock,
labst + insme AS total_quantity,
CASE WHEN labst > 0
THEN insme * 100 / labst
ELSE 0
END AS inspection_percentage
FROM mard
INTO TABLE @DATA(lt_stock_calc).

Fonctions de chaînes

16. CONCAT et opérations sur les chaînes

SELECT kunnr,
CONCAT( name1, CONCAT( ' (', CONCAT( ort01, ')' ) ) ) AS display_name,
LENGTH( name1 ) AS name_length,
LEFT( kunnr, 4 ) AS prefix
FROM kna1
INTO TABLE @DATA(lt_formatted).

17. SUBSTRING et REPLACE

SELECT vbeln,
SUBSTRING( vbeln, 1, 4 ) AS prefix,
REPLACE( vbeln, '00', 'XX' ) AS replaced
FROM vbak
INTO TABLE @DATA(lt_string_ops).

18. UPPER, LOWER, TRIM

SELECT name1,
UPPER( name1 ) AS upper_name,
LOWER( name1 ) AS lower_name,
LTRIM( name1, ' ' ) AS left_trimmed,
RTRIM( name1, ' ' ) AS right_trimmed
FROM kna1
INTO TABLE @DATA(lt_case_converted).

Fonctions de date

19. Calculs de date

SELECT vbeln, erdat,
DATS_DAYS_BETWEEN( erdat, @sy-datum ) AS days_ago,
DATS_ADD_DAYS( erdat, 30 ) AS plus_30_days,
DATS_ADD_MONTHS( erdat, 1 ) AS plus_1_month
FROM vbak
INTO TABLE @DATA(lt_with_dates).

20. Extraction de date

SELECT vbeln, erdat,
EXTRACT( YEAR FROM erdat ) AS year,
EXTRACT( MONTH FROM erdat ) AS month,
EXTRACT( DAY FROM erdat ) AS day
FROM vbak
INTO TABLE @DATA(lt_date_parts).
" Regroupement par année/mois
SELECT EXTRACT( YEAR FROM erdat ) AS year,
EXTRACT( MONTH FROM erdat ) AS month,
COUNT(*) AS order_count,
SUM( netwr ) AS total_value
FROM vbak
GROUP BY EXTRACT( YEAR FROM erdat ), EXTRACT( MONTH FROM erdat )
INTO TABLE @DATA(lt_monthly).

Conversion de type

21. CAST

SELECT vbeln,
CAST( netwr AS CHAR( 20 ) ) AS netwr_string,
CAST( '12345' AS INT4 ) AS number_value,
CAST( erdat AS CHAR( 8 ) ) AS date_string
FROM vbak
INTO TABLE @DATA(lt_casted).

22. COALESCE (gestion des NULL)

SELECT kunnr, name1,
COALESCE( name2, '' ) AS name2_safe,
COALESCE( ort01, 'Inconnu' ) AS city
FROM kna1
INTO TABLE @DATA(lt_with_defaults).

JOINs avancés

23. Cross Join

SELECT c~kunnr, y~year
FROM kna1 AS c
CROSS JOIN ( SELECT DISTINCT EXTRACT( YEAR FROM erdat ) AS year FROM vbak ) AS y
INTO TABLE @DATA(lt_customer_years).

24. Outer Apply (sous-requête corrélée)

SELECT c~kunnr, c~name1,
o~last_order, o~last_value
FROM kna1 AS c
LEFT OUTER JOIN (
SELECT kunnr,
MAX( erdat ) AS last_order,
MAX( netwr ) AS last_value
FROM vbak
GROUP BY kunnr
) AS o ON c~kunnr = o~kunnr
INTO TABLE @DATA(lt_with_last_order).

Sous-requêtes

25. Sous-requête scalaire

SELECT vbeln, kunnr, netwr,
( SELECT name1 FROM kna1 WHERE kunnr = vbak~kunnr ) AS customer_name
FROM vbak
INTO TABLE @DATA(lt_with_name).

26. EXISTS

SELECT kunnr, name1
FROM kna1 AS c
WHERE EXISTS (
SELECT * FROM vbak WHERE kunnr = c~kunnr AND erdat >= '20240101"
)
INTO TABLE @DATA(lt_active_customers).

27. IN avec sous-requête

SELECT * FROM kna1
WHERE kunnr IN ( SELECT DISTINCT kunnr FROM vbak WHERE netwr > 50000 )
INTO TABLE @DATA(lt_high_value_customers).

Conseils de performance

" 1. CTE au lieu de plusieurs sous-requêtes identiques
WITH
+high_value AS (
SELECT kunnr FROM vbak WHERE netwr > 50000
)
SELECT * FROM kna1
WHERE kunnr IN ( SELECT kunnr FROM +high_value )
INTO TABLE @lt_result.
" 2. Window Functions au lieu de sous-requêtes corrélées
" LENT :
SELECT vbeln, netwr,
( SELECT SUM( netwr ) FROM vbak AS inner WHERE inner~kunnr = outer~kunnr )
FROM vbak AS outer INTO TABLE @lt_slow.
" RAPIDE :
SELECT vbeln, netwr,
SUM( netwr ) OVER( PARTITION BY kunnr ) AS customer_total
FROM vbak INTO TABLE @lt_fast.
" 3. Filtrer tôt avec WHERE
WITH
+filtered AS (
SELECT * FROM vbak WHERE erdat >= '20240101' " Filtrer ici
)
SELECT * FROM +filtered ...

Remarques importantes / Bonnes pratiques

  • WITH (CTE) pour des sous-requêtes lisibles et réutilisables.
  • Window Functions pour le classement, les sommes cumulées et les comparaisons.
  • CASE pour des valeurs conditionnelles directement dans SELECT.
  • Literaux pour des valeurs constantes dans le résultat.
  • COALESCE pour des requêtes sûres avec NULL.
  • Utilisez les fonctions de date au lieu d’opérations sur les chaînes pour les dates.
  • ROW_NUMBER() OVER() pour la pagination et les requêtes Top-N.
  • Les CTEs peuvent être récursives pour les hiérarchies.
  • Vérifiez la version ABAP pour la disponibilité des fonctionnalités.
  • Combinez avec CDS Views pour des définitions réutilisables.