Ordenación virtual en ABAP: Claves secundarias y rendimiento

Kategorie
ABAP-Statements
Veröffentlicht
Autor
Johannes

La ordenación virtual mediante claves secundarias permite acceder a tablas internas en diferentes órdenes sin duplicar datos ni reordenarlos físicamente.

Concepto

Una tabla interna puede tener:

  • Clave primaria: Define el orden físico (para SORTED/HASHED)
  • Claves secundarias: Proporcionan accesos alternativos
" Tabla con claves secundarias
DATA: lt_orders TYPE SORTED TABLE OF ty_order
WITH UNIQUE KEY order_id
WITH NON-UNIQUE SORTED KEY by_customer COMPONENTS customer_id
WITH NON-UNIQUE SORTED KEY by_date COMPONENTS order_date.

Tipos de claves secundarias

SORTED KEY

" Clave secundaria ordenada
DATA: lt_data TYPE STANDARD TABLE OF ty_data
WITH EMPTY KEY
WITH NON-UNIQUE SORTED KEY by_name COMPONENTS name
WITH NON-UNIQUE SORTED KEY by_amount COMPONENTS amount DESCENDING.
" Acceso por clave secundaria
LOOP AT lt_data INTO DATA(ls_data) USING KEY by_name.
" Iteración ordenada por nombre
ENDLOOP.
READ TABLE lt_data INTO DATA(ls_found)
WITH KEY by_name COMPONENTS name = 'Test'.

HASHED KEY

" Clave secundaria hash (acceso O(1))
DATA: lt_data TYPE STANDARD TABLE OF ty_data
WITH EMPTY KEY
WITH UNIQUE HASHED KEY by_id COMPONENTS id.
" Acceso directo por hash
READ TABLE lt_data INTO DATA(ls_found)
WITH KEY by_id COMPONENTS id = 12345.

Ejemplos prácticos

Tabla de pedidos con múltiples accesos

TYPES: BEGIN OF ty_order,
order_id TYPE i,
customer_id TYPE i,
product_id TYPE string,
amount TYPE p DECIMALS 2,
order_date TYPE d,
status TYPE c LENGTH 1,
END OF ty_order.
" Definición con múltiples claves
DATA: lt_orders TYPE STANDARD TABLE OF ty_order
WITH EMPTY KEY
" Acceso rápido por ID de pedido
WITH UNIQUE HASHED KEY by_id
COMPONENTS order_id
" Búsqueda por cliente ordenada
WITH NON-UNIQUE SORTED KEY by_customer
COMPONENTS customer_id order_date
" Búsqueda por producto
WITH NON-UNIQUE SORTED KEY by_product
COMPONENTS product_id
" Ordenar por importe descendente
WITH NON-UNIQUE SORTED KEY by_amount
COMPONENTS amount DESCENDING.

Uso de las diferentes claves

" Llenar tabla
lt_orders = VALUE #(
( order_id = 1 customer_id = 100 product_id = 'A' amount = '500.00' order_date = '20241101' status = 'A' )
( order_id = 2 customer_id = 200 product_id = 'B' amount = '1500.00' order_date = '20241102' status = 'A' )
( order_id = 3 customer_id = 100 product_id = 'A' amount = '300.00' order_date = '20241103' status = 'P' )
( order_id = 4 customer_id = 300 product_id = 'C' amount = '2000.00' order_date = '20241104' status = 'A' )
( order_id = 5 customer_id = 100 product_id = 'B' amount = '750.00' order_date = '20241105' status = 'A' )
).
" 1. Buscar por ID (hash - O(1))
READ TABLE lt_orders INTO DATA(ls_order)
WITH KEY by_id COMPONENTS order_id = 3.
" 2. Todos los pedidos de un cliente (ordenados por fecha)
LOOP AT lt_orders INTO ls_order
USING KEY by_customer
WHERE customer_id = 100.
WRITE: / ls_order-order_id, ls_order-order_date.
ENDLOOP.
" 3. Top pedidos por importe
DATA(lv_count) = 0.
LOOP AT lt_orders INTO ls_order USING KEY by_amount.
lv_count = lv_count + 1.
IF lv_count > 3.
EXIT.
ENDIF.
WRITE: / 'Top', lv_count, ':', ls_order-amount.
ENDLOOP.
" 4. Pedidos de un producto
LOOP AT lt_orders INTO ls_order
USING KEY by_product
WHERE product_id = 'A'.
WRITE: / ls_order-order_id, ls_order-product_id.
ENDLOOP.

Comparación de rendimiento

Sin claves secundarias (ineficiente)

" MALO: Búsqueda lineal O(n)
LOOP AT lt_orders INTO DATA(ls_order)
WHERE customer_id = 100.
" ...
ENDLOOP.
" MALO: Ordenar cada vez
SORT lt_orders BY amount DESCENDING.
" Esto modifica el orden físico y pierde otros órdenes

Con claves secundarias (eficiente)

" BIEN: Búsqueda binaria O(log n)
LOOP AT lt_orders INTO DATA(ls_order)
USING KEY by_customer
WHERE customer_id = 100.
" ...
ENDLOOP.
" BIEN: Acceso ordenado sin modificar datos
LOOP AT lt_orders INTO ls_order
USING KEY by_amount.
" Ordenado por amount sin SORT
ENDLOOP.

Claves con múltiples componentes

DATA: lt_sales TYPE STANDARD TABLE OF ty_sales
WITH EMPTY KEY
WITH NON-UNIQUE SORTED KEY by_region_date
COMPONENTS region sales_date
WITH NON-UNIQUE SORTED KEY by_product_region
COMPONENTS product_id region.
" Acceso con clave parcial
LOOP AT lt_sales INTO DATA(ls_sale)
USING KEY by_region_date
WHERE region = 'EUROPE'. " Solo primer componente
" Iteración de todas las ventas de Europa, ordenadas por fecha
ENDLOOP.

Claves UNIQUE vs NON-UNIQUE

" UNIQUE: No permite duplicados en la combinación de componentes
DATA: lt_unique TYPE STANDARD TABLE OF ty_data
WITH UNIQUE HASHED KEY by_id COMPONENTS id.
" INSERT fallará si el ID ya existe
INSERT VALUE #( id = 1 name = 'Duplicado' ) INTO TABLE lt_unique.
IF sy-subrc <> 0.
" Clave duplicada
ENDIF.
" NON-UNIQUE: Permite duplicados
DATA: lt_non_unique TYPE STANDARD TABLE OF ty_data
WITH NON-UNIQUE SORTED KEY by_category COMPONENTS category.
" Múltiples entradas con la misma categoría permitidas

Modificación de datos con claves secundarias

" Las claves secundarias se actualizan automáticamente
INSERT VALUE #( order_id = 6 customer_id = 100 amount = '999.00' )
INTO TABLE lt_orders.
" Todas las claves secundarias se actualizan
" MODIFY también actualiza las claves
MODIFY TABLE lt_orders FROM VALUE #(
order_id = 1
amount = '600.00' " Cambio de amount
) USING KEY by_id.
" La clave by_amount se reordena automáticamente

Limitaciones

" No se pueden usar en SORTED/HASHED TABLE con clave primaria conflictiva
" INCORRECTO:
DATA: lt_bad TYPE SORTED TABLE OF ty_data
WITH UNIQUE KEY id
WITH UNIQUE SORTED KEY also_id COMPONENTS id. " Redundante

Caso de uso: Índices múltiples

CLASS zcl_order_index DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
TYPES: BEGIN OF ty_order,
order_id TYPE i,
customer_id TYPE i,
status TYPE c LENGTH 1,
amount TYPE p DECIMALS 2,
END OF ty_order.
TYPES: ty_orders TYPE STANDARD TABLE OF ty_order
WITH EMPTY KEY
WITH UNIQUE HASHED KEY by_id COMPONENTS order_id
WITH NON-UNIQUE SORTED KEY by_customer COMPONENTS customer_id
WITH NON-UNIQUE SORTED KEY by_status COMPONENTS status.
METHODS: get_by_id
IMPORTING iv_id TYPE i
RETURNING VALUE(rs_order) TYPE ty_order.
METHODS: get_by_customer
IMPORTING iv_customer_id TYPE i
RETURNING VALUE(rt_orders) TYPE ty_orders.
METHODS: get_by_status
IMPORTING iv_status TYPE c
RETURNING VALUE(rt_orders) TYPE ty_orders.
PRIVATE SECTION.
DATA: mt_orders TYPE ty_orders.
ENDCLASS.
CLASS zcl_order_index IMPLEMENTATION.
METHOD get_by_id.
READ TABLE mt_orders INTO rs_order
WITH KEY by_id COMPONENTS order_id = iv_id.
ENDMETHOD.
METHOD get_by_customer.
rt_orders = VALUE #(
FOR order IN mt_orders USING KEY by_customer
WHERE ( customer_id = iv_customer_id )
( order )
).
ENDMETHOD.
METHOD get_by_status.
rt_orders = VALUE #(
FOR order IN mt_orders USING KEY by_status
WHERE ( status = iv_status )
( order )
).
ENDMETHOD.
ENDCLASS.

Notas importantes / Mejores prácticas

  • Usa HASHED KEY para búsquedas por valor exacto (O(1)).
  • Usa SORTED KEY para rangos y ordenación (O(log n)).
  • Las claves secundarias ocupan memoria adicional.
  • El mantenimiento de claves tiene coste en INSERT/MODIFY/DELETE.
  • Considera el ratio lectura/escritura de tu aplicación.
  • Para tablas pequeñas (< 100 filas), el beneficio puede ser mínimo.
  • Combina con READ TABLE y LOOP AT.