ABAP Advanced Open SQL: WITH, Window Functions y más

Kategorie
ABAP-Statements
Veröffentlicht
Autor
Johannes

El Open SQL moderno (desde ABAP 7.50+) ofrece características potentes como Common Table Expressions (WITH), Window Functions y expresiones avanzadas. Estas funciones permiten consultas complejas directamente en ABAP.

Nuevas características por versión

CaracterísticaDesde versión
Declaración inline, variables host7.40 SP05
Expresiones CASE7.50
Literales, cálculos7.50
Funciones de cadena7.50
Window Functions7.50
WITH (CTE)7.51
Cross Join, Outer Apply7.52
Funciones de jerarquía7.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. Múltiples 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 con agregación

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 recursiva (jerarquías)

WITH
+hierarchy( node, parent, level ) AS (
" Nodos iniciales
SELECT org_id, parent_id, 1 AS level
FROM zorg_structure
WHERE parent_id IS INITIAL
UNION ALL
" Parte recursiva
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 - Numeración de filas

SELECT kunnr, vbeln, netwr,
ROW_NUMBER( ) OVER( PARTITION BY kunnr ORDER BY netwr DESC ) AS rank
FROM vbak
INTO TABLE @DATA(lt_ranked_orders).
" Solo Top-3 por cliente
SELECT * FROM @lt_ranked_orders AS t
WHERE t~rank <= 3
INTO TABLE @DATA(lt_top3).

6. RANK y 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 (huecos en valores iguales)
" DENSE_RANK: 1, 2, 2, 3 (sin huecos)

7. SUM OVER - Suma acumulativa

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 y LEAD - Fila anterior/siguiente

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).
" Calcular cambio respecto al mes anterior
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 y 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 - Agregación de ventana

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).
" Comparación con promedio
LOOP AT lt_with_stats INTO DATA(ls_order).
IF ls_order-netwr > ls_order-avg_order_value.
WRITE: / ls_order-vbeln, 'Por encima del promedio'.
ENDIF.
ENDLOOP.

Expresiones CASE

11. CASE WHEN

SELECT vbeln, netwr,
CASE
WHEN netwr >= 10000 THEN 'ALTO'
WHEN netwr >= 1000 THEN 'MEDIO'
ELSE 'BAJO'
END AS value_category
FROM vbak
INTO TABLE @DATA(lt_categorized).

12. CASE con campo

SELECT vbeln, auart,
CASE auart
WHEN 'TA' THEN 'Pedido estándar'
WHEN 'SO' THEN 'Pedido urgente'
WHEN 'RE' THEN 'Devolución'
ELSE 'Otro'
END AS order_type_text
FROM vbak
INTO TABLE @DATA(lt_with_text).

13. CASE en agregación

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).

Literales y cálculos

14. Literales en SELECT

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

15. Expresiones aritméticas

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).

Funciones de cadena

16. CONCAT y operaciones de cadena

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 y 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).

Funciones de fecha

19. Cálculos de fecha

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. Extracción de fecha

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).
" Agrupación por año/mes
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).

Conversión de tipos

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 (tratamiento de NULL)

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

JOINs extendidos

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 (Subconsulta correlacionada)

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).

Subconsultas

25. Subconsulta escalar

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 con subconsulta

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

Consejos de rendimiento

" 1. CTE en lugar de múltiples subconsultas iguales
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 en lugar de subconsultas correlacionadas
" LENTO:
SELECT vbeln, netwr,
( SELECT SUM( netwr ) FROM vbak AS inner WHERE inner~kunnr = outer~kunnr )
FROM vbak AS outer INTO TABLE @lt_slow.
" RÁPIDO:
SELECT vbeln, netwr,
SUM( netwr ) OVER( PARTITION BY kunnr ) AS customer_total
FROM vbak INTO TABLE @lt_fast.
" 3. Filtrar temprano con WHERE
WITH
+filtered AS (
SELECT * FROM vbak WHERE erdat >= '20240101' " Filtro aquí
)
SELECT * FROM +filtered ...

Notas importantes / Mejores prácticas

  • WITH (CTE) para subconsultas legibles y reutilizables.
  • Window Functions para ranking, sumas acumulativas y comparaciones.
  • CASE para valores condicionales directamente en SELECT.
  • Literales para valores constantes en el resultado.
  • COALESCE para consultas seguras ante NULL.
  • Usa funciones de fecha en lugar de operaciones de cadena sobre fechas.
  • ROW_NUMBER() OVER() para paginación y consultas Top-N.
  • Las CTEs pueden ser recursivas para jerarquías.
  • Verifica la versión de ABAP para disponibilidad de características.
  • Combina con CDS Views para definiciones reutilizables.