Ir al contenido
Programación ABAP

Novedades de ABAP 7.4 y 7.5

Cómo se escribía el código antes y cómo se escribe ahora. Muchas novedades que se asocian a «ABAP 7.5» llegaron con 7.40; cada ejemplo indica desde qué versión está disponible.

Ejemplos
39
Solo en 7.50
9
Versiones
7.02 a 7.50
7.02Plantillas de textoy funciones de texto7.40 SP02Declaraciones en líneaVALUE, NEW, COND7.40 SP05FOR, CORRESPONDINGSQL con @ y comas7.40 SP08REDUCE, FILTERLOOP … GROUP BY7.50IS INSTANCE OF, UNIONint8, TEST-SEAM7.5xCDS view entitiesRAP y ABAP Cloud
Las novedades del ABAP moderno llegaron poco a poco: muchas de las que se asocian a «ABAP 7.5» están disponibles desde 7.40. Después de 7.50 vinieron las CDS view entities, RAP y ABAP Cloud.

Declaraciones en línea

Se declara la variable en el mismo sitio donde se usa por primera vez. El tipo se deduce de lo que se le asigna.

Variables en una asignación

7.40 SP02

DATA(...) crea la variable con el tipo del valor de la derecha.

Antes
DATA lv_texto TYPE string.
DATA lv_total TYPE i.

lv_texto = 'Hola mundo'.
lv_total = lines( lt_vbap ).
Ahora
DATA(lv_texto) = `Hola mundo`.      " tipo string
DATA(lv_total) = lines( lt_vbap ).  " tipo i

Field-symbols en un LOOP

7.40 SP02

FIELD-SYMBOL(...) declara el field-symbol dentro del propio LOOP o READ TABLE.

Antes
FIELD-SYMBOLS <ls_vbap> TYPE vbap.

LOOP AT lt_vbap ASSIGNING <ls_vbap>.
  <ls_vbap>-netwr = <ls_vbap>-netwr * 2.
ENDLOOP.
Ahora
LOOP AT lt_vbap ASSIGNING FIELD-SYMBOL(<ls_vbap>).
  <ls_vbap>-netwr = <ls_vbap>-netwr * 2.
ENDLOOP.

Parámetros de salida de un método

7.40 SP02

La variable que recibe el resultado se declara en la propia llamada.

Antes
DATA lv_cliente TYPE kunnr.

lo_pedido->get_cliente(
  IMPORTING ev_cliente = lv_cliente ).
Ahora
lo_pedido->get_cliente(
  IMPORTING ev_cliente = DATA(lv_cliente) ).

Operadores constructores

VALUE, NEW, CONV, COND, SWITCH y CORRESPONDING construyen un valor en una sola expresión. Con # el tipo se toma del destino.

VALUE para estructuras

7.40 SP02

Rellena todos los campos de una vez.

Antes
DATA ls_direccion TYPE ty_direccion.

ls_direccion-calle  = 'Gran Vía'.
ls_direccion-numero = '28'.
ls_direccion-ciudad = 'Madrid'.
Ahora
DATA(ls_direccion) = VALUE ty_direccion(
  calle  = 'Gran Vía'
  numero = '28'
  ciudad = 'Madrid' ).

VALUE para tablas internas y rangos

7.40 SP02

Cada línea va entre paréntesis. Los campos comunes se escriben una sola vez delante.

Antes
DATA lt_rango TYPE RANGE OF matnr.
DATA ls_rango LIKE LINE OF lt_rango.

ls_rango-sign   = 'I'.
ls_rango-option = 'EQ'.
ls_rango-low    = 'MAT-001'.
APPEND ls_rango TO lt_rango.
ls_rango-low    = 'MAT-002'.
APPEND ls_rango TO lt_rango.
Ahora
DATA lt_rango TYPE RANGE OF matnr.

lt_rango = VALUE #( sign = 'I' option = 'EQ'
                    ( low = 'MAT-001' )
                    ( low = 'MAT-002' ) ).

NEW en lugar de CREATE OBJECT

7.40 SP02

Crea el objeto y lo asigna en una línea.

Antes
DATA lo_log TYPE REF TO zcl_log.

CREATE OBJECT lo_log
  EXPORTING
    iv_objeto = 'ZVENTAS'.
Ahora
DATA(lo_log) = NEW zcl_log( iv_objeto = 'ZVENTAS' ).

CONV para convertir al pasar parámetros

7.40 SP02

Ya no hace falta una variable auxiliar solo para cambiar el tipo.

Antes
DATA lv_texto TYPE string.

lv_texto = lv_cantidad.
lo_util->mostrar( lv_texto ).
Ahora
lo_util->mostrar( CONV string( lv_cantidad ) ).

COND en lugar de IF para asignar

7.40 SP02

Devuelve un valor según varias condiciones.

Antes
DATA lv_estado TYPE string.

IF lv_stock = 0.
  lv_estado = 'Agotado'.
ELSEIF lv_stock < 10.
  lv_estado = 'Pocas unidades'.
ELSE.
  lv_estado = 'Disponible'.
ENDIF.
Ahora
DATA(lv_estado) = COND string(
  WHEN lv_stock = 0  THEN `Agotado`
  WHEN lv_stock < 10 THEN `Pocas unidades`
  ELSE `Disponible` ).

SWITCH en lugar de CASE para asignar

7.40 SP02

Igual que COND, pero comparando un único valor.

Antes
DATA lv_saludo TYPE string.

CASE lv_idioma.
  WHEN 'S'.
    lv_saludo = 'Hola'.
  WHEN 'E'.
    lv_saludo = 'Hello'.
  WHEN OTHERS.
    lv_saludo = '?'.
ENDCASE.
Ahora
DATA(lv_saludo) = SWITCH string( lv_idioma
  WHEN 'S' THEN `Hola`
  WHEN 'E' THEN `Hello`
  ELSE `?` ).

CORRESPONDING para tablas completas

7.40 SP05

Copia por nombre de campo entre tablas internas. MAPPING asigna campos con nombres distintos.

Antes
LOOP AT lt_pedidos INTO ls_pedido.
  MOVE-CORRESPONDING ls_pedido TO ls_salida.
  ls_salida-cliente = ls_pedido-kunnr.
  APPEND ls_salida TO lt_salida.
ENDLOOP.
Ahora
lt_salida = CORRESPONDING #( lt_pedidos
                             MAPPING cliente = kunnr ).

LET para valores auxiliares

7.40 SP05

LET calcula un valor una sola vez y lo usa dentro de la expresión, sin declarar una variable aparte.

Antes
DATA lv_iva TYPE p LENGTH 15 DECIMALS 2.

lv_iva = ls_pedido-netwr * '0.21'.
ls_factura-base  = ls_pedido-netwr.
ls_factura-iva   = lv_iva.
ls_factura-total = ls_pedido-netwr + lv_iva.
Ahora
ls_factura = VALUE #(
  LET iva = ls_pedido-netwr * '0.21' IN
  base  = ls_pedido-netwr
  iva   = iva
  total = ls_pedido-netwr + iva ).

VALUE con BASE para añadir sin borrar

7.40 SP08

Sin BASE, VALUE crea la tabla de cero. Con BASE conserva las líneas que ya tenía y añade las nuevas.

Antes
ls_mat-matnr = 'M-01'.
APPEND ls_mat TO lt_mat.
ls_mat-matnr = 'M-02'.
APPEND ls_mat TO lt_mat.
Ahora
lt_mat = VALUE #( BASE lt_mat
                  ( matnr = 'M-01' )
                  ( matnr = 'M-02' ) ).

Expresiones de tabla

itab[ ... ] lee una línea de una tabla interna dentro de cualquier expresión. Si la línea no existe, lanza la excepción CX_SY_ITAB_LINE_NOT_FOUND.

Leer una línea

7.40 SP02

Con OPTIONAL (desde 7.40 SP08) devuelve una línea vacía en lugar de lanzar la excepción.

Antes
DATA ls_cliente TYPE kna1.

READ TABLE lt_clientes INTO ls_cliente
  WITH KEY kunnr = lv_kunnr.
IF sy-subrc = 0.
  lv_nombre = ls_cliente-name1.
ENDIF.
Ahora
" Si no existe, lanza CX_SY_ITAB_LINE_NOT_FOUND
DATA(lv_nombre) = lt_clientes[ kunnr = lv_kunnr ]-name1.

" Si no existe, devuelve una línea vacía
DATA(ls_cliente) = VALUE kna1(
  lt_clientes[ kunnr = lv_kunnr ] OPTIONAL ).

Comprobar si una línea existe

7.40 SP02

line_exists( ) devuelve verdadero o falso sin leer la línea.

Antes
READ TABLE lt_clientes TRANSPORTING NO FIELDS
  WITH KEY kunnr = lv_kunnr.
IF sy-subrc = 0.
  " el cliente ya está en la tabla
ENDIF.
Ahora
IF line_exists( lt_clientes[ kunnr = lv_kunnr ] ).
  " el cliente ya está en la tabla
ENDIF.

Obtener el índice de una línea

7.40 SP02

line_index( ) devuelve la posición, o 0 si no la encuentra.

Antes
READ TABLE lt_clientes TRANSPORTING NO FIELDS
  WITH KEY kunnr = lv_kunnr.
lv_indice = sy-tabix.
Ahora
DATA(lv_indice) = line_index( lt_clientes[ kunnr = lv_kunnr ] ).

Modificar un campo directamente

7.40 SP02

La expresión de tabla también vale como destino de una asignación.

Antes
FIELD-SYMBOLS <ls_vbap> TYPE vbap.

READ TABLE lt_vbap ASSIGNING <ls_vbap> INDEX 1.
IF sy-subrc = 0.
  <ls_vbap>-kwmeng = 5.
ENDIF.
Ahora
lt_vbap[ 1 ]-kwmeng = 5.

Valor por defecto si la línea no existe

7.40 SP08

Si la línea no existe, una expresión de tabla lanza CX_SY_ITAB_LINE_NOT_FOUND. Con DEFAULT u OPTIONAL dentro de VALUE no hay excepción.

Antes
READ TABLE lt_clientes INTO ls_cliente
  WITH KEY kunnr = lv_kunnr.
IF sy-subrc = 0.
  lv_nombre = ls_cliente-name1.
ELSE.
  lv_nombre = 'Desconocido'.
ENDIF.
Ahora
DATA(lv_nombre) = VALUE #(
  lt_clientes[ kunnr = lv_kunnr ]-name1
  DEFAULT 'Desconocido' ).

Plantillas de texto

El texto va entre barras verticales y las variables entre llaves. Llegaron en 7.02 y en 7.40 se usan en todas partes.

Montar un texto con variables

7.02

Sustituye a CONCATENATE y a las variables auxiliares para formatear números.

Antes
DATA lv_msg TYPE string.
DATA lv_num TYPE c LENGTH 10.

WRITE lv_total TO lv_num LEFT-JUSTIFIED.
CONCATENATE 'Pedido' lv_vbeln 'con' lv_num 'posiciones'
  INTO lv_msg SEPARATED BY space.
Ahora
DATA(lv_msg) = |Pedido { lv_vbeln } con { lv_total } posiciones|.

Formatos dentro de la plantilla

7.02

ALPHA, DATE, TIME, WIDTH, ALIGN, DECIMALS o CASE dan formato sin llamar a funciones.

Antes
CALL FUNCTION 'CONVERSION_EXIT_ALPHA_OUTPUT'
  EXPORTING
    input  = lv_matnr
  IMPORTING
    output = lv_matnr_sal.

WRITE sy-datum TO lv_fecha DD/MM/YYYY.
Ahora
DATA(lv_texto) =
  |Material { lv_matnr ALPHA = OUT }, fecha { sy-datum DATE = USER }|.

Concatenar con &&

7.02

El operador && une textos en cualquier expresión.

Antes
CONCATENATE lv_nombre lv_apellido
  INTO lv_completo SEPARATED BY space.
Ahora
lv_completo = lv_nombre && ` ` && lv_apellido.

Funciones de texto en lugar de sentencias

7.02

to_upper, to_lower, condense, replace, substring o shift_left devuelven el resultado, así que se pueden anidar.

Antes
lv_limpio = lv_texto.
TRANSLATE lv_limpio TO UPPER CASE.
CONDENSE lv_limpio.
Ahora
DATA(lv_limpio) = condense( to_upper( lv_texto ) ).

Unir las líneas de una tabla

7.02

concat_lines_of junta todas las líneas de una tabla de textos con el separador que indiques.

Antes
LOOP AT lt_nombres INTO lv_nombre.
  IF lv_lista IS INITIAL.
    lv_lista = lv_nombre.
  ELSE.
    CONCATENATE lv_lista lv_nombre
      INTO lv_lista SEPARATED BY ', '.
  ENDIF.
ENDLOOP.
Ahora
DATA(lv_lista) = concat_lines_of(
  table = lt_nombres sep = `, ` ).

FOR, REDUCE y FILTER

Recorren tablas internas dentro de una expresión: FOR construye tablas, REDUCE calcula un único valor y FILTER selecciona líneas.

Construir una tabla a partir de otra

7.40 SP05

FOR dentro de VALUE sustituye al LOOP con APPEND. Admite WHERE.

Antes
LOOP AT lt_vbap INTO ls_vbap WHERE netwr > 1000.
  ls_matnr-matnr = ls_vbap-matnr.
  APPEND ls_matnr TO lt_matnr.
ENDLOOP.
Ahora
lt_matnr = VALUE #( FOR ls_vbap IN lt_vbap
                    WHERE ( netwr > 1000 )
                    ( matnr = ls_vbap-matnr ) ).

Sumar con REDUCE

7.40 SP08

INIT da el valor inicial y NEXT lo actualiza en cada vuelta.

Antes
DATA lv_total TYPE netwr.

LOOP AT lt_vbap INTO ls_vbap.
  lv_total = lv_total + ls_vbap-netwr.
ENDLOOP.
Ahora
DATA(lv_total) = REDUCE netwr(
  INIT suma = CONV netwr( 0 )
  FOR ls_vbap IN lt_vbap
  NEXT suma = suma + ls_vbap-netwr ).

Filtrar líneas con FILTER

7.40 SP08

FILTER necesita una clave ordenada o hash sobre el campo filtrado; aquí, la clave secundaria k_estado.

Antes
LOOP AT lt_pedidos INTO ls_pedido WHERE estado = 'A'.
  APPEND ls_pedido TO lt_abiertos.
ENDLOOP.
Ahora
DATA(lt_abiertos) = FILTER #( lt_pedidos USING KEY k_estado
                              WHERE estado = 'A' ).

Contar con REDUCE y una condición

7.40 SP08

FOR admite WHERE también dentro de REDUCE: aquí cuenta los pedidos abiertos sin recorrerlos a mano.

Antes
DATA lv_abiertos TYPE i.

LOOP AT lt_pedidos INTO ls_pedido WHERE estado = 'A'.
  lv_abiertos = lv_abiertos + 1.
ENDLOOP.
Ahora
DATA(lv_abiertos) = REDUCE i(
  INIT n = 0
  FOR ls_pedido IN lt_pedidos WHERE ( estado = 'A' )
  NEXT n = n + 1 ).

Agrupar con LOOP AT … GROUP BY

Agrupa las líneas de una tabla interna por cualquier criterio, sin ordenarla antes y sin AT NEW.

Recorrer posiciones agrupadas por pedido

7.40 SP08

LOOP AT GROUP recorre las líneas de cada grupo. Con AT NEW los campos a la derecha de la clave salían con asteriscos.

Antes
SORT lt_vbap BY vbeln.

LOOP AT lt_vbap INTO ls_vbap.
  AT NEW vbeln.
    WRITE / ls_vbap-vbeln.
  ENDAT.
  WRITE / ls_vbap-matnr.
ENDLOOP.
Ahora
LOOP AT lt_vbap INTO DATA(ls_vbap)
     GROUP BY ls_vbap-vbeln INTO DATA(lv_pedido).
  WRITE / lv_pedido.
  LOOP AT GROUP lv_pedido INTO DATA(ls_pos).
    WRITE / ls_pos-matnr.
  ENDLOOP.
ENDLOOP.

Open SQL moderno

Las variables de ABAP llevan @, los campos se separan con comas e INTO va al final. 7.50 añade FIELDS, UNION, expresiones @( ) y nuevas funciones SQL.

Sintaxis con @, comas e INTO al final

7.40 SP08

INTO TABLE @DATA(...) declara la tabla con los campos exactos de la consulta.

Antes
DATA lt_vbak TYPE TABLE OF vbak.

SELECT vbeln erdat kunnr
  FROM vbak
  INTO CORRESPONDING FIELDS OF TABLE lt_vbak
  WHERE kunnr = lv_kunnr.
Ahora
SELECT vbeln, erdat, kunnr
  FROM vbak
  WHERE kunnr = @lv_kunnr
  INTO TABLE @DATA(lt_vbak).

Cálculos y CASE en la base de datos

7.40 SP05

El cálculo lo hace la base de datos y no hace falta un LOOP después.

Antes
SELECT vbeln netwr FROM vbak INTO TABLE lt_vbak.

LOOP AT lt_vbak ASSIGNING <ls_vbak>.
  IF <ls_vbak>-netwr > 10000.
    <ls_vbak>-tipo = 'GRANDE'.
  ELSE.
    <ls_vbak>-tipo = 'NORMAL'.
  ENDIF.
ENDLOOP.
Ahora
SELECT vbeln, netwr,
       CASE WHEN netwr > 10000 THEN 'GRANDE'
            ELSE 'NORMAL'
       END AS tipo
  FROM vbak
  INTO TABLE @DATA(lt_vbak).

FROM primero con FIELDS

7.50

Escribir antes la tabla permite al editor de Eclipse proponer los campos.

Antes
SELECT vbeln, erdat, netwr
  FROM vbak
  WHERE erdat >= @lv_desde
  INTO TABLE @DATA(lt_vbak).
Ahora
SELECT FROM vbak
  FIELDS vbeln, erdat, netwr
  WHERE erdat >= @lv_desde
  INTO TABLE @DATA(lt_vbak).

Unir dos consultas con UNION

7.50

UNION quita los duplicados; UNION ALL los mantiene. INTO va después de la última consulta.

Antes
SELECT kunnr name1 FROM kna1
  INTO TABLE lt_socios
  WHERE land1 = 'ES'.

SELECT lifnr name1 FROM lfa1
  APPENDING TABLE lt_socios
  WHERE land1 = 'ES'.
Ahora
SELECT kunnr AS id, name1 FROM kna1
  WHERE land1 = 'ES'
UNION
SELECT lifnr AS id, name1 FROM lfa1
  WHERE land1 = 'ES'
  INTO TABLE @DATA(lt_socios).

Expresiones ABAP dentro de la consulta

7.50

@( ... ) evalúa una expresión ABAP y pasa el resultado a la consulta.

Antes
DATA lv_desde TYPE d.

lv_desde = sy-datum - 30.
SELECT vbeln FROM vbak
  INTO TABLE lt_vbeln
  WHERE erdat >= lv_desde.
Ahora
SELECT vbeln FROM vbak
  WHERE erdat >= @( sy-datum - 30 )
  INTO TABLE @DATA(lt_vbeln).

Funciones de texto y redondeo en SQL

7.50

Nuevas funciones: CONCAT, LPAD, LENGTH, LTRIM, RTRIM, REPLACE, RIGHT, SUBSTRING y ROUND.

Antes
SELECT matnr brgew FROM mara INTO TABLE lt_mara.

LOOP AT lt_mara ASSIGNING <ls_mara>.
  <ls_mara>-prefijo = <ls_mara>-matnr(3).
  <ls_mara>-peso    = round( val = <ls_mara>-brgew dec = 1 ).
ENDLOOP.
Ahora
SELECT matnr,
       substring( matnr, 1, 3 ) AS prefijo,
       round( brgew, 1 )        AS peso
  FROM mara
  INTO TABLE @DATA(lt_mara).

Objetos, lógica y excepciones

Comprobar el tipo de un objeto sin capturar excepciones, usar métodos como condiciones y lanzar excepciones con un mensaje.

Comprobar el tipo con IS INSTANCE OF

7.50

Comprueba si el cast es posible antes de hacerlo. CAST hace la conversión en la misma línea.

Antes
DATA lo_camion TYPE REF TO zcl_camion.

TRY.
    lo_camion ?= lo_vehiculo.
    lo_camion->cargar( ).
  CATCH cx_sy_move_cast_error.
ENDTRY.
Ahora
IF lo_vehiculo IS INSTANCE OF zcl_camion.
  CAST zcl_camion( lo_vehiculo )->cargar( ).
ENDIF.

Distinguir tipos con CASE TYPE OF

7.50

Cada WHEN TYPE comprueba un tipo y deja el objeto ya convertido en una variable.

Antes
DATA lo_camion TYPE REF TO zcl_camion.
DATA lo_coche  TYPE REF TO zcl_coche.

TRY.
    lo_camion ?= lo_vehiculo.
    lo_camion->cargar( ).
  CATCH cx_sy_move_cast_error.
    TRY.
        lo_coche ?= lo_vehiculo.
        lo_coche->aparcar( ).
      CATCH cx_sy_move_cast_error.
    ENDTRY.
ENDTRY.
Ahora
CASE TYPE OF lo_vehiculo.
  WHEN TYPE zcl_camion INTO DATA(lo_camion).
    lo_camion->cargar( ).
  WHEN TYPE zcl_coche INTO DATA(lo_coche).
    lo_coche->aparcar( ).
  WHEN OTHERS.
    " otro tipo de vehículo
ENDCASE.

Métodos como condición y xsdbool

7.40 SP08

Un método que devuelve un valor se puede usar directamente como condición. xsdbool( ) convierte una condición en abap_true o abap_false.

Antes
IF lo_pedido->es_urgente( ) = abap_true.
  lv_urgente = abap_true.
ELSE.
  lv_urgente = abap_false.
ENDIF.
Ahora
DATA(lv_urgente) = xsdbool( lo_pedido->es_urgente( ) ).

Lanzar una excepción con un mensaje

7.50

MESSAGE asocia un mensaje de la SE91 a la excepción. La clase debe implementar IF_T100_DYN_MSG.

Antes
RAISE EXCEPTION TYPE zcx_ventas
  EXPORTING
    textid = zcx_ventas=>pedido_bloqueado
    vbeln  = lv_vbeln.
Ahora
RAISE EXCEPTION TYPE zcx_ventas
  MESSAGE e001(zventas) WITH lv_vbeln.

Nuevo tipo int8 y pruebas con test seams

Un entero de 8 bytes para contadores grandes y una forma de sustituir partes del código en las pruebas unitarias.

Enteros grandes con int8

7.50

Va de −9.223.372.036.854.775.808 a +9.223.372.036.854.775.807. En el diccionario es el tipo INT8.

Antes
" i solo llega a 2.147.483.647
DATA lv_contador TYPE p LENGTH 16 DECIMALS 0.
Ahora
DATA lv_contador TYPE int8.

Sustituir código en pruebas con TEST-SEAM

7.50

TEST-INJECTION, en la clase de test, reemplaza el bloque marcado. Solo en clases y grupos de funciones.

Antes
METHOD leer_cliente.
  SELECT SINGLE * FROM kna1 INTO rs_kna1
    WHERE kunnr = iv_kunnr.
ENDMETHOD.

" Para probarlo había que sacar la lectura
" a una interfaz e inyectar un objeto simulado.
Ahora
METHOD leer_cliente.
  TEST-SEAM lectura_bd.
    SELECT SINGLE * FROM kna1
      WHERE kunnr = @iv_kunnr
      INTO @rs_kna1.
  END-TEST-SEAM.
ENDMETHOD.

" En la clase de test:
TEST-INJECTION lectura_bd.
  rs_kna1-kunnr = '0000001000'.
  rs_kna1-name1 = 'Cliente de prueba'.
END-TEST-INJECTION.

Fuentes

Documentación oficial de palabras clave ABAP de SAP: