Mesh Expressions en ABAP: Navegación en tablas enlazadas

Kategorie
ABAP-Statements
Veröffentlicht
Autor
Johannes

Mesh Expressions permiten navegar entre tablas internas relacionadas de forma declarativa. Son especialmente útiles cuando trabajas con estructuras de datos jerárquicas como cabecera-posiciones.

Concepto básico

Un Mesh define relaciones entre tablas internas, similar a las asociaciones en CDS Views. Esto permite navegar de una entrada a las entradas relacionadas en otra tabla.

Definición de Mesh

Sintaxis básica

TYPES: BEGIN OF MESH ty_mesh,
header TYPE STANDARD TABLE OF ty_header WITH EMPTY KEY,
items TYPE SORTED TABLE OF ty_item WITH UNIQUE KEY parent_id item_id,
" Asociación: header -> items
_items TYPE ASSOCIATION ty_mesh-header TO ty_mesh-items
ON parent_id = header_id,
END OF MESH ty_mesh.

Ejemplo completo de definición

TYPES: BEGIN OF ty_order,
order_id TYPE i,
customer_id TYPE i,
order_date TYPE d,
END OF ty_order.
TYPES: BEGIN OF ty_item,
order_id TYPE i,
item_id TYPE i,
product TYPE string,
quantity TYPE i,
price TYPE p DECIMALS 2,
END OF ty_item.
TYPES: BEGIN OF ty_customer,
customer_id TYPE i,
name TYPE string,
END OF ty_customer.
" Definición del Mesh con asociaciones
TYPES: BEGIN OF MESH ty_order_mesh,
orders TYPE SORTED TABLE OF ty_order WITH UNIQUE KEY order_id,
items TYPE SORTED TABLE OF ty_item WITH UNIQUE KEY order_id item_id,
customers TYPE SORTED TABLE OF ty_customer WITH UNIQUE KEY customer_id,
" Orden -> Posiciones
_items FOR orders TYPE ASSOCIATION ty_order_mesh-orders TO ty_order_mesh-items
ON order_id = order_id,
" Orden -> Cliente
_customer FOR orders TYPE ASSOCIATION ty_order_mesh-orders TO ty_order_mesh-customers
ON customer_id = customer_id,
" Posición -> Orden
_order FOR items TYPE ASSOCIATION ty_order_mesh-items TO ty_order_mesh-orders
ON order_id = order_id,
END OF MESH ty_order_mesh.

Uso del Mesh

Llenar el Mesh

DATA: ls_mesh TYPE ty_order_mesh.
" Llenar pedidos
ls_mesh-orders = VALUE #(
( order_id = 1 customer_id = 100 order_date = '20241101' )
( order_id = 2 customer_id = 200 order_date = '20241102' )
( order_id = 3 customer_id = 100 order_date = '20241103' )
).
" Llenar posiciones
ls_mesh-items = VALUE #(
( order_id = 1 item_id = 10 product = 'Producto A' quantity = 5 price = '100.00' )
( order_id = 1 item_id = 20 product = 'Producto B' quantity = 3 price = '50.00' )
( order_id = 2 item_id = 10 product = 'Producto C' quantity = 1 price = '200.00' )
( order_id = 3 item_id = 10 product = 'Producto A' quantity = 2 price = '100.00' )
).
" Llenar clientes
ls_mesh-customers = VALUE #(
( customer_id = 100 name = 'Cliente A' )
( customer_id = 200 name = 'Cliente B' )
).
" Obtener el primer pedido
DATA(ls_order) = ls_mesh-orders[ 1 ].
" Navegar a las posiciones de este pedido
DATA(lt_items) = ls_mesh-_items[ ls_order ].
" Mostrar
LOOP AT lt_items INTO DATA(ls_item).
WRITE: / ls_item-product, ls_item-quantity.
ENDLOOP.
" Desde pedido al cliente
DATA(ls_order) = ls_mesh-orders[ order_id = 1 ].
DATA(lt_customers) = ls_mesh-_customer[ ls_order ].
DATA(ls_customer) = lt_customers[ 1 ].
WRITE: / 'Cliente:', ls_customer-name.
" O en una sola expresión
DATA(lv_customer_name) = ls_mesh-_customer[ ls_mesh-orders[ order_id = 1 ] ][ 1 ]-name.
" Desde posición al pedido
DATA(ls_item) = ls_mesh-items[ order_id = 1 item_id = 10 ].
DATA(lt_orders) = ls_mesh-_order[ ls_item ].
DATA(ls_parent_order) = lt_orders[ 1 ].
WRITE: / 'Pedido padre:', ls_parent_order-order_id, ls_parent_order-order_date.

Casos de uso avanzados

Calcular totales por pedido

LOOP AT ls_mesh-orders INTO DATA(ls_order).
DATA(lt_order_items) = ls_mesh-_items[ ls_order ].
DATA(lv_total) = REDUCE decfloat34(
INIT sum = CONV decfloat34( 0 )
FOR item IN lt_order_items
NEXT sum = sum + ( item-quantity * item-price )
).
WRITE: / 'Pedido:', ls_order-order_id, 'Total:', lv_total.
ENDLOOP.

Filtrar con condiciones

" Todos los pedidos del cliente 100
DATA(lt_customer_orders) = VALUE ty_order_mesh-orders(
FOR order IN ls_mesh-orders
WHERE ( customer_id = 100 )
( order )
).
" Para cada uno, obtener las posiciones
LOOP AT lt_customer_orders INTO DATA(ls_order).
DATA(lt_items) = ls_mesh-_items[ ls_order ].
" Procesar posiciones...
ENDLOOP.

Comprobar existencia

" ¿Tiene posiciones el pedido?
DATA(ls_order) = ls_mesh-orders[ order_id = 1 ].
DATA(lt_items) = ls_mesh-_items[ ls_order ].
IF lt_items IS NOT INITIAL.
WRITE: / 'Pedido tiene', lines( lt_items ), 'posiciones'.
ELSE.
WRITE: / 'Pedido sin posiciones'.
ENDIF.

Comparación con alternativas

Sin Mesh (clásico)

" Clásico: Buscar posiciones manualmente
LOOP AT lt_orders INTO DATA(ls_order).
LOOP AT lt_items INTO DATA(ls_item)
WHERE order_id = ls_order-order_id.
" Procesar...
ENDLOOP.
ENDLOOP.

Con Mesh

" Con Mesh: Navegación declarativa
LOOP AT ls_mesh-orders INTO DATA(ls_order).
DATA(lt_order_items) = ls_mesh-_items[ ls_order ].
LOOP AT lt_order_items INTO DATA(ls_item).
" Procesar...
ENDLOOP.
ENDLOOP.

Requisitos de clave

Las asociaciones solo funcionan correctamente si las tablas tienen las claves apropiadas:

" Tabla ordenada con clave única - mejor rendimiento
items TYPE SORTED TABLE OF ty_item WITH UNIQUE KEY order_id item_id,
" Tabla estándar con clave vacía - funciona, pero menos eficiente
items TYPE STANDARD TABLE OF ty_item WITH EMPTY KEY,

Manejo de errores

" Comprobar si la navegación devuelve resultados
TRY.
DATA(lt_items) = ls_mesh-_items[ ls_mesh-orders[ order_id = 999 ] ].
CATCH cx_sy_itab_line_not_found.
WRITE: / 'Pedido no encontrado'.
ENDTRY.
" Alternativa con OPTIONAL
DATA(ls_order) = VALUE #( ls_mesh-orders[ order_id = 999 ] OPTIONAL ).
IF ls_order IS NOT INITIAL.
DATA(lt_items) = ls_mesh-_items[ ls_order ].
ENDIF.

Notas importantes / Mejores prácticas

  • Usa SORTED TABLE con claves para mejor rendimiento.
  • Los Meshes son ideales para estructuras cabecera-posiciones.
  • La navegación es solo lectura - no puedes modificar a través del Mesh.
  • Las asociaciones deben definirse para ambas direcciones si necesitas navegación bidireccional.
  • Considera usar CDS Views para datos de base de datos con relaciones.
  • Los Meshes funcionan bien con VALUE y FOR.