
“FIND SELECT FOR POOL/CLUSTER TAB WITHOUT ORDER BY”
Este mensaje significa que en tu código estás haciendo un:
SELECT ... FROM [tabla pool o cluster] INTO ...
...sin usar una cláusula ORDER BY, y eso es potencialmente problemático. Pero más allá del ORDER BY, también está el tema de las tablas pool/cluster.
¿Qué son las tablas pool/cluster?
Son un tipo antiguo de tablas en SAP:
TipoCaracterística
| Pooled Tables |
Varias tablas lógicas almacenadas en una única tabla física. |
| Cluster Tables |
Datos comprimidos de varias tablas almacenados juntos. |
Estas tablas no existen como "una tabla individual" en la base de datos. Son manejadas internamente por SAP, y no se comportan igual que tablas transparentes.
Problemas con SELECT sin ORDER BY en tablas pool/cluster:
-
SAP HANA no admite bien las pooled/cluster tables.
En sistemas HANA, muchas de estas tablas se deben reemplazar por estructuras más modernas.
-
El orden de los datos NO está garantizado.
Y como son estructuras especiales, el acceso puede ser impredecible.
-
El rendimiento puede verse afectado.
Un SELECT sin ORDER BY sobre tablas pool/cluster puede comportarse distinto entre bases de datos (MaxDB, Oracle, HANA...).
Recomendación:
-
Si usas un SELECT sobre una tabla pool/cluster, SIEMPRE deberías usar ORDER BY si el orden importa.
-
Mejor aún: evita usar tablas pool/cluster en desarrollos nuevos.
¿Usar la cláusula WHERE controla los datos transferidos?
Sí, totalmente.
La cláusula WHERE filtra los datos directamente en la base de datos, por lo tanto:
-
Reduce la cantidad de datos transferidos desde la BD al servidor de aplicación.
-
Mejora el rendimiento del programa (menos carga, menos memoria, más rápido).
-
Evita traer registros que no necesitas.
-
EJ: SELECT * FROM zclientes INTO TABLE @DATA(lt_clientes).
-------> Estás trayendo todos los registros de la tabla zclientes a tu programa, incluso si después solo usas unos pocos.
- En cambio si utilizas :
- SELECT * FROM zclientes INTO TABLE @DATA(lt_clientes)
- WHERE pais = 'AR'.
Estás trayendo solo los clientes de Argentina, y el resto ni siquiera viaja por la red, ni consume memoria en ABAP.
Ventajas de usar WHERE
Ventaja Explicación
| Menor tráfico |
Solo llegan al programa los registros que cumplen la condición. |
| Más rendimiento |
La base de datos filtra más rápido que ABAP. |
| Menor uso de memoria |
Tu tabla interna no se llena innecesariamente. |
| Código más claro |
Estás especificando exactamente qué necesitas. |
¿La cláusula HAVING controla los datos transferidos como WHERE?
En parte sí, pero su función es diferente.
-
WHERE: Filtra registros individuales antes del agrupamiento (en la base de datos).
-
HAVING: Filtra después de hacer un GROUP BY, es decir, filtra grupos agregados.
Ambas reducen datos, pero en momentos distintos del procesamiento.
Diferencias clave entre WHERE y HAVING
Característica WHERE HAVING
| ¿Cuándo filtra? |
Antes del GROUP BY |
Después del GROUP BY |
| ¿Sobre qué actúa? |
Filas individuales |
Grupos (agregados) |
| ¿Usa funciones agregadas (SUM, COUNT, etc.)? |
No |
Sí |
| ¿Reduce datos transferidos? |
Sí, directamente |
Sí, pero después de agrupar |
¿HAVING también ayuda a reducir datos?
Sí, pero:
-
No reduce el número de filas leídas desde la tabla (como lo hace WHERE).
-
Filtra resultados agrupados, que también ahorra memoria y mejora rendimiento en el programa.
- HAVING siempre se utiliza junto con GROUP BY
Porque su propósito es precisamente filtrar los grupos que se crean después del GROUP BY.
Sin GROUP BY, no hay grupos que filtrar, así que HAVING no tendría sentido.
¿Entonces cómo lo pensamos?
Cláusula ¿Sobre qué actúa? ¿Cuándo se usa?
| WHERE |
Filas individuales |
Siempre, con o sin GROUP BY |
| HAVING |
Grupos agregados |
Solo con GROUP BY |
¿los datos innecesarios de una tabla interna se determinan usando DELETE?
Sí, se pueden eliminar usando DELETE, pero lo ideal es evitarlos desde el principio.
Explicación:
Hay dos enfoques:
1. Enfoque óptimo: evitar traer datos innecesarios
"Mejor no cargar lo que no vas a usar."
- Usá un SELECT con cláusula WHERE bien definida.
- Filtrás en la base de datos, no en el ABAP.
SELECT * FROM zclientes INTO TABLE @DATA(lt_clientes)
WHERE pais = 'AR' AND activo = 'X'.
- Aquí ya evitás cargar clientes inactivos o de otros países.
- Más rápido, más limpio, menos memoria.
Enfoque correctivo: limpiar después con DELETE
"Si ya los tenés en la tabla, podés eliminarlos."
DELETE lt_clientes WHERE activo <> 'X'.
Funciona, pero:
¿Qué dicen las
buenas prácticas ABAP (reglas de oro)?
Regla Aplicación
- Evitar traer datos innecesarios
|
- Mejor usar SELECT ... WHERE con condiciones.
|
|
|
- Si ya tenés la tabla cargada, podés usar DELETE.
|
- Nunca traer todo y filtrar luego sin motivo
|
- Evita SELECT * sin condiciones seguido de DELETE.
|
¿Para qué usamos UP TO n ROWS en ABAP?
La cláusula UP TO n ROWS se usa en una sentencia SELECT para limitar la cantidad de registros que se traen desde la base de datos.
✅ ¿Qué hace exactamente?
Te permite decirle al sistema:
“Traeme como máximo n registros.”
SELECT * FROM zventas
INTO TABLE @DATA(lt_ventas)
UP TO 10 ROWS.
Esto traerá máximo 10 registros desde la tabla zventas, sin importar cuántos cumplan la condición (si hay).
¿Por qué es útil?
Uso Ejemplo
|
|
Cargar pocos registros sin saturar memoria |
|
|
Vista previa de datos en ALV, reportes, etc. |
|
|
Evitar traer millones de registros innecesarios |
|
|
Cuando se implementa scroll o paginado en apps |
¡¡¡Pero ojo con algo importante:
¡UP TO n ROWS no garantiza orden!
Si querés, por ejemplo, los primeros 10 registros más recientes, tenés que usar ORDER BY también:
SELECT * FROM zventas
INTO TABLE @DATA(lt_top10)
WHERE estado = 'A'
ORDER BY fecha DESC
UP TO 10 ROWS.
- Esto ahora sí garantiza que traés las 10 ventas más recientes.
¿Y si estás usando CDS views?
En CDS, se recomienda usar TOP y ORDER BY, no UP TO.
Pero en ABAP puro, UP TO n ROWS sigue siendo válido.
En resumen:
Cláusula Función
|
|
- Limita la cantidad de registros que se traen del SELECT
|
| ¿Reduce datos transferidos? |
|
| ¿Aumenta performance? |
- Mucho, si se usa correctamente
|
| ¿Garantiza orden? |
- No, se recomienda usar ORDER BY junto a ella
|
Qué hace DISTINCT en ABAP?
La cláusula DISTINCT se utiliza en una sentencia SELECT para que el resultado no contenga registros duplicados.
Es decir, te devuelve solo combinaciones únicas de columnas seleccionadas.
¿Qué hace exactamente?
-
SAP ejecuta una eliminación de duplicados en base a los campos seleccionados.
-
Solo se devuelven filas únicas para esa combinación de columnas.
Ejemplo práctico:
Supongamos que zclientes tiene esto:
nombre ciudad
| Ana |
Buenos Aires |
| Ana |
Buenos Aires |
| Luis |
Córdoba |
| Ana |
Mendoza |
Sin DISTINCT:
SELECT nombre ciudad INTO TABLE @DATA(lt_clientes) FROM zclientes.
Resultado: 4 registros (incluyendo duplicados)
Con DISTINCT:
SELECT DISTINCT nombre ciudad INTO TABLE @DATA(lt_clientes) FROM zclientes.
Resultado tres registros.Se elimina el duplicado.
¿Cuándo usar DISTINCT?
Cuándo usarlo Por qué
| Evitar registros duplicados |
Cuando los datos traídos se repiten |
| Para llenar listas sin repetición |
Ej. dropdowns de ciudades, países, etc. |
| Al hacer estadísticas o conteos únicos |
Para contar sin duplicados |
Consideraciones importantes
-
DISTINCT actúa solo sobre los campos del SELECT.
Si incluís más campos, la probabilidad de duplicados baja (¡o desaparece!).
-
Puede impactar en rendimiento.
Porque la base de datos debe ordenar y eliminar duplicados. Usalo cuando realmente lo necesites.
-
No se puede usar DISTINCT con GROUP BY.
Están pensados para cosas diferentes.
¿Qué son las funciones agregadas?
Las funciones agregadas (o funciones de agregación) son funciones que operan sobre un conjunto de registros y devuelven un único valor como resultado.
En vez de trabajar fila por fila, trabajan sobre grupos de datos.
Ejemplos comunes de funciones agregadas en ABAP (y SQL en general):
Función ¿Qué hace?
| SUM( campo ) |
Devuelve la suma total de los valores |
| AVG( campo ) |
Calcula el promedio |
| MAX( campo ) |
Devuelve el valor máximo |
| MIN( campo ) |
Devuelve el valor mínimo |
| COUNT( * ) o COUNT( campo ) |
Cuenta la cantidad de registros |
Ejemplo simple en ABAP:
Supongamos que tenés una tabla de ventas zventas.
-
SELECT SUM( monto ) AS total_ventas
FROM zventas
INTO @DATA(lv_total).
Esto te da un único valor: la suma total de todas las ventas.
- ¿Dónde se usan?
En resumen:
Concepto Función
| Funciones agregadas |
Operan sobre un conjunto de filas para devolver un solo valor |
| Uso típico |
SUM, AVG, COUNT, MIN, MAX |
| Muy comunes en |
SELECTs con GROUP BY, reportes, CDS, análisis |
Escenario ficticio: Ventas por cliente
Supongamos que tenemos la siguiente tabla zventas con los campos:
cliente_id monto fecha
| C001 |
100 |
2024-01-01 |
| C001 |
200 |
2024-02-15 |
| C002 |
300 |
2024-03-10 |
| C003 |
150 |
2024-01-20 |
| C001 |
250 |
2024-04-05 |
| C002 |
100 |
2024-04-02 |
Objetivo:
Mostrar por cada cliente:
TYPES: BEGIN OF ty_resumen,
cliente_id TYPE zventas-cliente_id,
total TYPE p DECIMALS 2,
promedio TYPE p DECIMALS 2,
maximo TYPE p DECIMALS 2,
minimo TYPE p DECIMALS 2,
cantidad TYPE i,
END OF ty_resumen.
DATA: lt_resumen TYPE STANDARD TABLE OF ty_resumen.
SELECT
cliente_id,
SUM( monto ) AS total,
AVG( monto ) AS promedio,
MAX( monto ) AS maximo,
MIN( monto ) AS minimo,
COUNT( * ) AS cantidad
FROM zventas
GROUP BY cliente_id
INTO TABLE @lt_resumen.
LOOP AT lt_resumen INTO DATA(ls_cliente).
WRITE: / 'Cliente:', ls_cliente-cliente_id,
' Total:', ls_cliente-total,
' Promedio:', ls_cliente-promedio,
' Máximo:', ls_cliente-maximo,
' Mínimo:', ls_cliente-minimo,
' Cantidad:', ls_cliente-cantidad.
ENDLOOP.
¿Qué hace este código?
-
Agrupa las ventas por cliente.
-
Calcula 5 funciones agregadas.
-
Muestra un pequeño resumen para cada cliente.
-
Resultado esperado (en consola):
Cliente: C001 Total: 550.00 Promedio: 183.33 Máximo: 250.00 Mínimo: 100.00 Cantidad: 3
Cliente: C002 Total: 400.00 Promedio: 200.00 Máximo: 300.00 Mínimo: 100.00 Cantidad: 2
Cliente: C003 Total: 150.00 Promedio: 150.00 Máximo: 150.00 Mínimo: 150.00 Cantidad: 1
Vamos a convertir el ejemplo de ventas por cliente con funciones agregadas en una vista CDS (Core Data Services), ideal para exponer a Fiori, OData o simplemente reutilizar en ABAP.
Objetivo:
Crear una CDS View que agrupe las ventas por cliente y muestre:
Supongamos que la tabla base es zventas, con los campos:
cliente_id
- monto
- fecha
CDS View: ZI_VentasResumen
@AbapCatalog.sqlViewName: 'ZV_VENTAS_RES' @AbapCatalog.compiler.compareFilter: true @AccessControl.authorizationCheck: #NOT_REQUIRED @EndUserText.label: 'Resumen de ventas por cliente' define view ZI_VentasResumen as select from zventas { key cliente_id, sum( monto ) as total_ventas, avg( monto ) as promedio, max( monto ) as monto_maximo, min( monto ) as monto_minimo, count( * ) as cantidad } group by cliente_id
¿Qué estamos haciendo aquí?
ElementoSignificado
| @AbapCatalog.sqlViewName |
Nombre técnico de la vista en la base de datos |
| define view ZI_VentasResumen |
Nombre semántico en CDS |
| sum(...), avg(...), etc. |
Funciones agregadas sobre los datos |
| group by cliente_id |
Agrupamos por cliente |
| key cliente_id |
Lo definimos como clave de la vista |
Reducir el número de ejecuciones de consulta (SELECTs) es una muy buena práctica.
¿Por qué?
Cada vez que ejecutás una sentencia SELECT, el sistema hace una llamada al motor de base de datos, lo cual implica:
Entonces, si podés hacer menos consultas, ¡tu código será más eficiente!
Mal ejemplo: muchos SELECT individuales
LOOP AT lt_clientes INTO DATA(ls_cliente).
SELECT SINGLE nombre INTO ls_cliente-nombre
FROM zclientes
WHERE cliente_id = ls_cliente-cliente_id.
" otras operaciones...
ENDLOOP.
Este código hace 1 SELECT por cada cliente. Si tenés 1,000 clientes, hacés 1,000 consultas
Buen ejemplo: un solo SELECT con JOIN o FOR ALL ENTRIES Opción 1: JOIN
SELECT a~cliente_id, a~venta_id, b~nombre
FROM zventas AS a
INNER JOIN zclientes AS b ON a~cliente_id = b~cliente_id
INTO TABLE @DATA(lt_ventas_con_nombre).
Opción 2: FOR ALL ENTRIES
SELECT * FROM zclientes INTO TABLE @DATA(lt_clientes_base). IF lt_clientes_base IS NOT INITIAL. SELECT cliente_id, nombre FROM zclientes INTO TABLE @DATA(lt_info_clientes) FOR ALL ENTRIES IN lt_clientes_base WHERE cliente_id = lt_clientes_base-cliente_id. ENDIF.
Beneficios de reducir los SELECTs:
BeneficioPor qué
|
|
Menos llamadas = más rápido |
|
|
Reduce presión en base de datos |
|
|
Menos repetición, más lógica en un solo paso |
|
|
Aprovecha code pushdown y procesamiento en la BD |
"Traé todos los datos que necesitás en una sola consulta si es posible.
No hagas SELECTs dentro de loops."
¿Cómo minimizar realmente el esfuerzo de búsqueda en ABAP?
Te dejo las técnicas más efectivas y prácticas, con ejemplos:
1.
Usar campos indexados en la cláusula WHERE
Cuando hacés un SELECT, asegurate de filtrar por campos que tengan índice, como:
Ejemplo optimizado:
SELECT * FROM zfacturas INTO TABLE @DATA(lt_facturas) WHERE cliente_id = 'C001' AND fecha BETWEEN '20240101' AND '20240331'.
Si cliente_id y fecha están en un índice → muy rápido.
2.
Evitar funciones sobre campos en el WHERE
Si aplicás funciones (como UPPER, +, LEFT, CAST, etc.) en los campos del WHERE, rompés el uso del índice.
Malo:
SELECT * FROM zclientes WHERE UPPER(nombre) = 'JUAN'. " Rompe el índice
Bueno:
DATA(nombre_upper) = 'JUAN'. SELECT * FROM zclientes WHERE nombre = nombre_upper.
3.
Usar FOR ALL ENTRIES en lugar de SELECT dentro de LOOP
En vez de hacer 1000 SELECT SINGLE dentro de un LOOP, traé todo con un solo SELECT usando FOR ALL ENTRIES.
Bueno:
SELECT * FROM zclientes INTO TABLE @DATA(lt_clientes). IF lt_clientes IS NOT INITIAL. SELECT cliente_id, nombre FROM zventas INTO TABLE @DATA(lt_ventas) FOR ALL ENTRIES IN lt_clientes WHERE cliente_id = lt_clientes-cliente_id. ENDIF.
4.
Ordenar las condiciones del WHERE según los índices
Aunque SAP y la base de datos optimizan esto, poner los campos del índice primero ayuda a la claridad y optimización.
" Suponiendo que el índice es (cliente_id, fecha) SELECT * FROM zventas WHERE cliente_id = 'C005' AND fecha > '20240101'.
5.
Usar SELECT SINGLE cuando solo necesitás una fila
No uses SELECT ... INTO TABLE si solo necesitás un único registro.
Correcto:
SELECT SINGLE nombre INTO @DATA(lv_nombre) FROM zclientes WHERE cliente_id = 'C001'.
6.
Evitar traer más campos de los necesarios
Menos datos = menos carga = mejor rendimiento.
Malo:
SELECT * FROM zclientes INTO TABLE @DATA(lt_data).
Mejor:
SELECT cliente_id, nombre FROM zclientes INTO TABLE @DATA(lt_data).
7.
Analizar con ST05 (SQL Trace)
Usá la transacción ST05 para ver si tu consulta está usando un índice o está haciendo full table scan.
Te dice:
-
Qué índice usó
-
Cuántos registros leyó
-
Tiempo que tardó
En resumen:
Técnica Resultado
| WHERE con campos indexados |
|
| Sin funciones sobre campos |
- Permite el acceso directo
|
| Un solo SELECT con FOR ALL ENTRIES |
|
| Traer solo campos necesarios |
|
| Usar SELECT SINGLE con clave |
|
- Cuando hablamos de buffers en SAP, hay un concepto fundamental que aparece en los entornos con múltiples servidores de aplicación (sistemas distribuidos): los buffers cross-application server, también conocidos como:
Cross-Application Server Buffers (también llamados
cross-instance buffers)
¿Qué son?
Los cross-application server buffers permiten que el buffer de una tabla esté disponible para todos los servidores de aplicación en un entorno SAP distribuido.
Es decir: si un dato se carga en un servidor SAP, otros servidores también pueden accederlo desde memoria, sin tener que consultar la base de datos de nuevo.
¿Por qué existen?
En entornos SAP grandes (como productivos), es común que haya varios servidores de aplicación SAP conectados a una sola base de datos:
[ Servidor App A ] ←→ | [ Servidor App B ] ←→ [ Base de datos SAP HANA ] | [ Servidor App C ] ←→
Sin cross-buffering, cada servidor tendría su propia copia del buffer, y eso puede causar:
¿Qué hace el
cross-server buffering?
- Sincroniza el buffer entre todos los servidores de aplicación.
- Si un dato se actualiza en el servidor A, ese cambio se propaga a B, C, etc.
- Se mantiene la consistencia de los datos bufferizados en todo el landscape SAP.
¿Dónde se configura?
En SE11 → Technical Settings de la tabla:
Verás esta opción:
Buffering: Allowed → Buffering Type: Single record / Generic area / Full → Buffer synchronization: *Across all application servers*
- Si está marcado: la tabla usa cross-application buffering.
- Si no: cada servidor mantiene su propio buffer (más rápido, pero menos consistente).
Cuándo
usar cross-server buffering:
Situación
¿Por qué?
| Tablas de configuración global |
Todos los servidores deben ver los mismos valores |
| Catálogos como países, monedas, unidades |
Los cambios deben propagarse al instante |
| Tablas pequeñas y estables |
Poca carga, mucho beneficio |
Cuándo
NO usar cross-buffer:
Situación
Motivo
| Tablas que cambian con frecuencia |
Genera tráfico de sincronización innecesario |
| Alto volumen de escrituras |
Impacta el rendimiento del buffer |
| Datos específicos por servidor |
No tiene sentido compartirlos |
En resumen:
Concepto
Explicación
| Cross-server buffer |
Hace que los datos bufferizados estén disponibles para todos los servidores de aplicación |
| ¿Dónde se configura? |
En SE11 → Technical Settings |
| ¿Para qué sirve? |
Mantener consistencia de datos en entornos distribuidos |
| ¿Cuándo usarlo? |
Tablas pequeñas, de lectura frecuente y cambios poco frecuentes |