Ir al contenido
Programación ABAP

Sentencias SQL

Leer y grabar en la base de datos con Open SQL y trabajar con tablas internas.

Manual ABAP
6 de 14

Antes y ahora

Este capítulo se escribió para ABAP clásico. Las ideas siguen valiendo, pero casi todas las sentencias se escriben hoy de otra forma. Este es el resumen; en cada apartado tienes la comparación con código y, plegado, el texto original.

ConceptoEn el manualHoyEstado
NombreOpen SQLABAP SQLSe escribe distinto
Variables en la consultaWHERE erdat >= lv_desdeWHERE erdat >= @lv_desdeSe escribe distinto
Lista de camposSeparados por espaciosSeparados por comas; INTO al finalSe escribe distinto
Tabla de destinoDeclarada antes con DATA … OCCURSDeclarada en la consulta: INTO TABLE @DATA(lt_pedidos)Se escribe distinto
Leer y escribir a la vezSELECT … ENDSELECT con WRITE dentroLeer todo con INTO TABLE y mostrarlo con ALVSustituido
Datos de varias tablasFOR ALL ENTRIES o SELECT anidadosJOIN o una vista CDSSe escribe distinto
Recorrer tablas internasCabecera implícita: LOOP AT itab.Área de trabajo o field-symbol: LOOP AT itab ASSIGNING FIELD-SYMBOL(<fs>)Sustituido
Leer una líneaREAD TABLE … WITH KEY … BINARY SEARCHExpresión de tabla itab[ campo = valor ] y tablas SORTED o HASHEDSe escribe distinto
Contar líneasDESCRIBE TABLE itab LINES nlines( itab )Se escribe distinto
Vaciar una tablaREFRESH itabCLEAR itabSustituido
SubtotalesAT NEW / AT END OF con SUMLOOP AT … GROUP BY y REDUCESe escribe distinto

Sigue igual: COMMIT WORK y ROLLBACK WORK, las cláusulas WHERE, GROUP BY y ORDER BY, SORT, DELETE ADJACENT DUPLICATES, LOOP AT … WHERE.

6.1. Sentencias OPEN SQL

Se escribe distinto

Así se hace hoy

El SELECT se escribe con comas, las variables de ABAP llevan @ y el destino va al final. En lugar de leer registro a registro con SELECT … ENDSELECT, se lee todo de una vez en una tabla interna, que es mucho más rápido en HANA.

Antes
TABLES vbak.

SELECT * FROM vbak
  WHERE erdat >= lv_desde
    AND vkorg = p_vkorg.
  WRITE: / vbak-vbeln, vbak-netwr.
ENDSELECT.
Ahora
SELECT FROM vbak
  FIELDS vbeln, kunnr, netwr, waerk
  WHERE erdat >= @lv_desde
    AND vkorg = @p_vkorg
  INTO TABLE @DATA(lt_pedidos).

cl_salv_table=>factory(
  IMPORTING r_salv_table = DATA(lo_alv)
  CHANGING  t_table      = lt_pedidos ).
lo_alv->display( ).

Los cálculos, los CASE y las uniones se hacen ahora en la propia consulta, y la base de datos devuelve el resultado ya preparado:

Antes
SELECT vbeln kunnr netwr
  FROM vbak
  INTO CORRESPONDING FIELDS OF TABLE lt_pedidos
  WHERE erdat >= lv_desde.

IF lt_pedidos[] IS NOT INITIAL.
  SELECT vbeln posnr matnr
    FROM vbap
    INTO TABLE lt_pos
    FOR ALL ENTRIES IN lt_pedidos
    WHERE vbeln = lt_pedidos-vbeln.
ENDIF.
Ahora
SELECT FROM vbak AS c
  INNER JOIN vbap AS p ON p~vbeln = c~vbeln
  FIELDS c~vbeln, c~kunnr, p~posnr, p~matnr,
         CASE c~auart WHEN 'TA' THEN 'Normal'
                      ELSE 'Otro' END AS tipo
  WHERE c~erdat >= @lv_desde
  ORDER BY c~vbeln, p~posnr
  INTO TABLE @DATA(lt_pedidos).
  • Pide solo los campos que necesitas: SELECT * en tablas anchas como MARA o VBAK desperdicia memoria y red.
  • Con FOR ALL ENTRIES, comprueba siempre que la tabla de entrada no está vacía: si lo está, se ignora el WHERE y se lee toda la tabla.
  • Si una consulta se repite en varios programas, conviértela en una vista CDS y léela con SELECT como si fuera una tabla.
Texto original del manual: sintaxis clásica

Se llaman sentencias Open SQL a aquellas propias de ABAP que simulan un subconjunto de sentencias SQL (“Structured Query Language”). En general, las sentencias SQL (del tipo que sean) se usan para el acceso a datos en tablas. Las Open SQL tratan tablas de bases de datos (es decir, las tablas del Diccionario de Datos) mientras que las Native SQL son sentencias propias de cada base de datos, que se escriben entre EXEC SQL y ENDEXEC).

Las sentencias Open SQL son: SELECT (lectura de registros de una tabla), INSERT (inserción de un registro en una tabla), UPDATE (modificación de un registro), OPEN CURSOR (abre un cursor), FETCH (avanza una posición el cursor), CLOSE CURSOR (cierra un cursor), COMMIT WORK (actualiza los últimos cambios de la BD, confirmando que se hacen las modificaciones), ROLLBACK WORK (deshace las modificaciones hechas a la BD volviendo al último checkpoint).

  • 6.1.1. Sentencia SELECT

    • SELECT [ SINGLE ]campos FROM tabla[ INTO … | APPENDING … ] [ WHERE …][ GROUP BY campos][ ORDER BY criterio].…[ ENDSELECT ].
    • Con SELECT se leen registros de una tabla del Diccionario de Datos (no vale para leer de una tabla interna). La tabla se indica tras FROM. Se pueden especificar los campos a leer (si se desean leer todos los campos de la tabla, poner un ‘*’ como ‘campos’) y si se trata de una lectura individual o múltiple (con SINGLE sólo se lee el primer registro que cumpla los criterios WHERE de búsqueda, acabando el SELECT. En este caso no se debe cerrar éste con ENDSELECT. Y sin SINGLE se leen todas las entradas que concuerden con dichos criterios). Devuelve SY-SUBRC 0 si encuentra algo (y en SY-DBCNT el nº de entradas leídas); y si no, 4. Parámetros:

      • [ SINGLE [ FOR UPDATE ]][DISTINCT] { * | campos | MAX(c) AS m | MIN(c) AS m | AVG(c) AS m | SUM(c) AS m | COUNT(c) AS m | COUNT DISTINCT(c) AS m | COUNT(*) AS m }: Con DISTINCT se excluyen automáticamente las líneas duplicadas. Se puede especificar, en lugar de campos a leer, un parámetro ‘AS’ para que el SELECT simplemente calcule un valor numérico, como el máximo, mínimo, media, suma, … de todas las entradas (numéricas) encontradas.
      • FROM tabla | vista | (tabla) [ CLIENT SPECIFIED ] [ BYPASSING BUFFER ] [ UP TO n ROWS ]: Este ‘parámetro’ es obligatorio. Especifica la tabla de la base de datos a leer. La tabla o vista debe existir en el D.D. No hace falta declararla con TABLES si se lee con INTO. Con (tabla) puede especificarse la tabla o vista dinámicamente (aquí ‘tabla’ es una variable). El mandante se especifica con CLIENT SPECIFIED. Con BYPASSING BUFFER se lee directamente de la base de datos sin pasar por el buffer de SAP. Se usa UP TO n ROWS para leer hasta una línea determinada (si n = 0, se leen todas; si n < 0, es un error).
      • APPENDING[ CORRESPONDING FIELDS OF ] TABLE tabla[ PACKAGE SIZE n] Añade (INTO machaca) directamente los registros leídos en la tabla interna especificada.
      • INTO[ CORRESPONDING FIELDS OF ] { field-string | TABLE tabla } [ PACKAGE SIZE n ]: Se usa para indicar al sistema el lugar donde debe dejar el dato leído (en una tabla interna o en un registro), y si guarda todos los campos o bien sólo los iguales en nombre. De esta forma no hace falta moverlos con un MOVE de la tabla de la BD a la tabla interna, basta hacer directamente el APPEND o COLLECT.
      • WHERE campo_BD operador campo: En las cláusulas WHERE se definen los criterios de selección. Serán seleccionadas todas las entradas (si existen) de la tabla que cumplan estos criterios (o sólo la primera si es un SELECT SINGLE). Sin WHERE se lee toda la tabla. Primero se escribe siempre el campo de la tabla de la BD (sólo el nombre del campo, sin el nombre de la tabla) y luego el campo o variable con el que se compara. Los operadores pueden ser: EQ, NE, LT, LE, GT, GE, =, <> o bien ><, <, <=, >, >=. Hay otros operadores especiales: [ NOT ] BETWEEN c1 AND c2, [ NOT ] LIKE c [ ESCAPE e ], [ NOT ] IN (c1, …, cn), [ NOT ] IN criterio_selección,IS [ NOT ] NULL (tabla_condiciones).
      • GROUP BY campo1 … campon (tabla): Esta opción permite combinar el contenido de un grupo de entradas de una tabla de la BD, en una única línea. Los campos a agrupar pueden especificarse de forma estática (en tiempo de compilación) o dinámica (en ejecución).
      • ORDER BY[ PRIMARY KEY ] campo1 [ ASCENDING | DESCENDING ] … campon [ ASCENDING | DESCENDING ](tabla): Este parámetro sirve para definir el orden de tratamiento (criterio de ordenación) de las líneas seleccionadas por el SELECT. Con PRIMARY KEY la tabla se ordena por la clave primaria. Se puede ordenar por varios campos, de forma ascendente (por defecto) o descendente. También puede especificarse el criterio de ordenación a través de una tabla (definida con entradas de 72 caracteres máximo. En cada línea de la tabla se especifica un criterio de ordenación).

      Ejemplo:

      ABAP
      SELECT * FROM name WHERE campo1 = 'valor1'
                           AND campo2 = 'valor2'.
      
        WRITE: / name-campo1, name-campo2.
      
      ENDSELECT.
Programa ABAPSELECT … FROMInterfaz de base de datostraduce a SQL de la BDBúfer de tablasen el servidor de aplicaciónBase de datos (SAP HANA)tablas, vistas CDS y procedimientossi no está en el búferNative SQL, ADBCy AMDP: sin búfer✓ añade el mandante✓ igual en cualquier BD✓ comprueba la sintaxis
ABAP SQL pasa por la interfaz de base de datos, que añade el mandante, usa el búfer de tablas y traduce la consulta para la base de datos instalada.

6.1.2. Sentencia INSERT

Se escribe distinto

Así se hace hoy

INSERT, UPDATE, MODIFY y DELETE siguen existiendo; lo único que cambia es que las variables y tablas de ABAP llevan @:

Antes
INSERT zventas FROM wa_venta.

INSERT zventas FROM TABLE it_ventas
  ACCEPTING DUPLICATE KEYS.

UPDATE zventas SET importe = lv_importe
  WHERE cliente = lv_cliente.
Ahora
INSERT zventas FROM @ls_venta.

INSERT zventas FROM TABLE @lt_ventas
  ACCEPTING DUPLICATE KEYS.

UPDATE zventas SET importe = @lv_importe
  WHERE cliente = @lv_cliente.

Escribe solo en tablas propias. Las tablas estándar de SAP (VBAK, MARA…) se actualizan con BAPIs o, en S/4HANA, con los objetos RAP y EML: así se aplican todas las validaciones. En ABAP Cloud no se permite escribir directamente en tablas de SAP.

Texto original del manual: sintaxis clásica
  • INSERT [ INTO ] { tabla | (c) } [ CLIENT SPECIFIED ] [ VALUES field-string | FROM field-string ] | FROM TABLE tabla_interna[ ACCEPTING DUPLICATE KEYS ] ].
  • Con INSERT se añaden nuevas entradas a una tabla del Diccionario de Datos (para una tabla interna se haría con APPEND o COLLECT). Dicha tabla puede especificarse de forma directa, con ‘tabla’, o indirecta, con ‘(c)’. En ambos casos ésta debe existir en el D.D. Con FROM TABLE inserta en una sola operación todas las entradas de la tabla_interna. Con CLIENT SPECIFIED se pueden insertar registros en la base de datos sobre otro mandante. El área de trabajo especificada en el field-string de VALUES debe ser tan ancha como el área de trabajo de la tabla de la base de datos. Con la opción ACCEPTING DUPLICATE KEYS, si una línea no puede ser insertada no se aborta la sentencia, aunque sigue devolviendo SY-SUBRC = 4.

6.2. Sentencias de tablas internas

Se escribe distinto

Así se hace hoy

Las sentencias de tablas internas siguen ahí, pero sin cabecera: se trabaja con un área de trabajo o un field-symbol, y muchas operaciones tienen ya una función o una expresión equivalente.

Antes
LOOP AT it_pedidos.
  it_pedidos-netwr = it_pedidos-netwr * 2.
  MODIFY it_pedidos.
ENDLOOP.

DESCRIBE TABLE it_pedidos LINES lv_n.
REFRESH it_pedidos.
Ahora
LOOP AT lt_pedidos ASSIGNING FIELD-SYMBOL(<ls_pedido>).
  <ls_pedido>-netwr = <ls_pedido>-netwr * 2.
ENDLOOP.

DATA(lv_n) = lines( lt_pedidos ).
CLEAR lt_pedidos.
Antes
SORT it_pedidos BY vbeln.
READ TABLE it_pedidos
  WITH KEY vbeln = lv_vbeln BINARY SEARCH.
IF sy-subrc = 0.
  lv_importe = it_pedidos-netwr.
ENDIF.
Ahora
" Con una tabla SORTED o HASHED la búsqueda ya es rápida
lv_importe = VALUE #( lt_pedidos[ vbeln = lv_vbeln ]-netwr
                      OPTIONAL ).

Los subtotales con AT END OF obligaban a ordenar antes y a que el campo estuviera a la izquierda de la estructura. Con GROUP BY eso deja de importar:

Antes
SORT lt_ventas BY cliente.
LOOP AT lt_ventas INTO ls_venta.
  AT END OF cliente.
    SUM.
    WRITE: / ls_venta-cliente,
             ls_venta-importe.
  ENDAT.
ENDLOOP.
Ahora
TYPES ty_importe TYPE p LENGTH 15 DECIMALS 2.

LOOP AT lt_ventas INTO DATA(ls_venta)
     GROUP BY ls_venta-cliente INTO DATA(lv_cliente).
  DATA(lv_total) = REDUCE ty_importe(
    INIT s = 0
    FOR v IN GROUP lv_cliente
    NEXT s = s + v-importe ).
  cl_demo_output=>write( |{ lv_cliente }: { lv_total }| ).
ENDLOOP.
cl_demo_output=>display( ).
Texto original del manual: sintaxis clásica

Corrección: la versión anterior de esta página llamaba «Native SQL» a estas sentencias, pero son las sentencias de tablas internas. Native SQL es el SQL propio de cada base de datos, que se escribe entre EXEC SQL y ENDEXEC (o con la clase CL_SQL_STATEMENT) y se salta las comprobaciones de ABAP.

Estas sentencias trabajan con tablas internas, es decir, con datos que están en la memoria del programa. Son: LOOP, READ TABLE, DESCRIBE TABLE, MODIFY, WRITE TO, DELETE, SORT, CLEAR, REFRESH, FREE.

  • LOOP AT tabla_interna [ INTO área_trabajo ] [ FROM índice1 ] [ TO índice2 ][ WHERE condición ] [ TRANSPORTING NO FIELDS ]. … ENDLOOP.
    Lee entradas de una tabla interna, recorriéndola en un bucle. Cada entrada se lee y se envía a un área de trabajo. Con INTO se indica un área de trabajo distinta a la cabecera de la tabla (el área por defecto). Si no hay entradas en la tabla no se entra en el bucle, y se tendrá SY-SUBRC = 4 (también si ninguna entrada cumple las condiciones del WHERE). En cada vuelta, el campo SY-TABIX contiene el índice de la entrada actual. Con FROM y TO se delimita el rango de entradas a procesar, de una inicial a otra final. Con TRANSPORTING NO FIELDS no se copia la entrada procesada en el área de trabajo (se gana velocidad si por ejemplo sólo se desea contar el número de entradas, no procesarlas). Esta opción se puede usar junto con WHERE pero nunca con INTO. Con la sentencia CONTINUE puede pasarse a la vuelta siguiente, y con EXIT se abandona el bucle.
  • READ TABLE tabla_interna [ INTO at ] { INDEX i | WITH KEY c1 = v1 ... cn = vn | WITH KEY = valor | WITH KEY clave } [ BINARY SEARCH ][ COMPARING c1 ... cn | COMPARING ALL FIELDS ][ TRANSPORTING c1 ... cn | TRANSPORTING NO FIELDS ].
    Lee una línea de una tabla interna, a través de un índice o una clave (es más rápido que la búsqueda mediante LOOP … WHERE). Si se encuentra una entrada, devuelve SY-SUBRC = 0 y en SY-TABIX el índice de esa entrada; y SY-SUBRC = 4 en otro caso. Ocurre un error en ejecución si el índice a buscar es negativo; si es mayor que el número de entradas, se retorna SY-SUBRC = 4. Con INTO puede especificarse un área de trabajo distinta a la cabecera de la tabla.
  • DESCRIBE TABLE tabla_interna [ LINES nr_líneas ] [ OCCURS occ ].
    Pregunta información al sistema respecto a la tabla interna especificada. Con LINES se actualiza en la variable ‘nr_líneas’ el número de líneas de dicha tabla (esto es útil por ejemplo para saber si la tabla está vacía o no, antes de intentar mostrarla por pantalla). Con OCCURS, guarda en ‘occ’ el número de entradas con que está definida la tabla.
  • MODIFY tabla_interna [ FROM área_trabajo ] [INDEX índice ] [ TRANSPORTING c1 ... cn | (campos) [ WHERE condición ] ].
    Modifica el contenido de una entrada de una tabla interna, a partir de la información del área de trabajo especificada con FROM, o por defecto de la cabecera. Con INDEX se especifica la entrada a modificar (entonces no es necesario escribir el MODIFY dentro de un LOOP), o bien se busca dicha entrada con la condición del WHERE. Con TRANSPORTING sólo se actualizan los campos especificados (que se pueden dar de forma dinámica).
  • WRITE campo[offset1] TO tabla_interna[offset2] INDEX índice.
    Actualiza la entrada de índice especificado de la tabla interna dada. Los offset permiten escribir en sólo una parte de la entrada de la tabla, y se escriben así: +número, todo pegado al nombre del campo. La sentencia similar WRITE … TO pero sin INDEX sólo sirve para variables simples.
  • DELETE tabla_interna [ INDEX índice ] [ FROM índice1 TO índice2 ] [ WHERE cond ][ ADJACENT DUPLICATES FROM itab [ COMPARING { f1...fn | ALL FIELDS } ] ].
    Borra entradas de una tabla interna. Sin INDEX se usa dentro de un LOOP, y con INDEX fuera. Con ADJACENT DUPLICATES borra las entradas duplicadas conservando la primera. Dos líneas son consideradas duplicadas si sus campos clave son iguales, o bien si los campos f1 ... fn son iguales (si se especifican estos), o bien si todos sus campos son iguales (con ALL FIELDS).
  • SORT tabla_interna [ ASCENDING | DESCENDING ] [ AS TEXT ][ BY campo1 [ASCENDING | DESCENDING ] AS TEXT ] … [ BY campon [ASCENDING | DESCENDING ] AS TEXT ].
    Ordena una tabla interna por los criterios especificados. Por defecto, de forma ascendente respecto a los campos clave. Para ordenar por otro(s) campo(s), indicarlo con BY.
  • CLEAR tabla_interna | campo.
    Limpia o borra el área de trabajo de la tabla (la cabecera), pero no modifica ninguna entrada de la misma. También es aplicable a una variable simple (CLEAR está sobrecargado), y su efecto es borrarla (la pone a su valor inicial: SPACE o cero).
  • REFRESH tabla_interna.
    Limpia el contenido de la tabla interna, borrando todas sus entradas, pero preservando el valor de su cabecera.
  • FREE tabla_interna | campo.
    Libera el espacio ocupado en memoria por la tabla interna. También es aplicable a variables simples, destruyéndolas.

6.3. SENTENCIAS AT:

  • Las sentencias AT permiten controlar el procesamiento de una tabla interna, dentro de un LOOP. Sólo se usan por tanto dentro de bucles LOOP. Permiten ejecutar condicionalmente un bloque de sentencias, dentro del bucle, según se cumpla o no una u otra condición. Son: AT NEW, AT END OF, AT FIRST, AT LAST.
  • AT NEW campo. … ENDAT.
    Con AT NEW se ejecuta el bloque cuando el campo especificado tiene un nuevo valor, es decir, que se activa al cambiar el campo su valor (se activa también en la primera vuelta, pues el campo pasará de INITIAL a su primer valor, pero no en la última vuelta, pues el campo no variará su valor). La tabla debe estar ordenada por ese campo y por los que están a su izquierda en la estructura, porque un cambio en cualquiera de ellos también activa el bloque.
  • AT END OF campo. … ENDAT.
    Con AT END OF se ejecuta el bloque cuando el campo especificado alcanza su último valor. La tabla debe estar ordenada por ese campo y por los que están a su izquierda en la estructura, porque un cambio en cualquiera de ellos también activa el bloque.
  • AT FIRST.…ENDAT.
    Con AT FIRST se ejecuta el bloque en la primera vuelta del LOOP.
  • AT LAST.…ENDAT.
    Con AT LAST se ejecuta el bloque en la última vuelta del LOOP.
  • Dentro de AT NEW y AT END OF, la sentencia SUM suma todos los campos numéricos de las entradas procesadas en estos bloques de proceso. Dentro de AT FIRST y AT LAST, suma los campos numéricos de todas las entradas de la tabla.

    Ejemplo:

    ABAP
    SORT lt_ventas BY cliente.
    LOOP AT lt_ventas INTO ls_venta.
      AT END OF cliente.
        SUM.                                " subtotal de cada cliente
        WRITE: / ls_venta-cliente, ls_venta-importe.
      ENDAT.
      AT LAST.
        SUM.                                " total de toda la tabla
        WRITE: / 'Total', ls_venta-importe.
      ENDAT.
    ENDLOOP.

Para seguir

  • Toda la sintaxis nueva, con más ejemplos de antes y ahora: New ABAP 7.5.
  • Modelos de datos reutilizables y con semántica: vistas CDS.
  • Lógica que no cabe en una consulta, ejecutada dentro de HANA: AMDP.
  • Tablas de SAP explicadas campo a campo, con su SELECT moderno: referencia de tablas.