Mostrando entradas con la etiqueta UPDATE. Mostrar todas las entradas
Mostrando entradas con la etiqueta UPDATE. Mostrar todas las entradas

viernes, 2 de marzo de 2012

COBOL/CICS vol.2: acceso a ficheros.

En este artículo veremos un programa CICS que recoge información de la pantalla y actualiza un fichero VSAM. Al igual que en el ejemplo anterior, se trata de un programa conversacional, es decir, el código se ejecutará de principio a fin sin devolver el control al CICS en ningún momento (salvo al finalizar la ejecución).

En el artículo COBOL/CICS vol.1: primer contacto vimos como crear una transacción CICS y asociarla a nuestro programa, por lo que no volveremos a explicarlo.
En esta ocasión veremos como definir un fichero VSAM en CICS para que pueda ser utilizado por nuestro programa. El sistema será el mismo que para definir un programa.

Utilizaremos la transacción CEDA para definir el fichero.
Escribiremos CEDA DEFINE FILE
La información a rellenar será:
File
Group
DSNAme
Add = yes
BRowse = yes
DELete = yes
READ = yes
UPDATE = yes

Una vez definido, tendremos que instalarlo:
CEDA INSTALL FILE(FILE1)
Se debe rellenar el campo Group


Ahora si consultamos la transacción CEMT veremos que ya aparece nuestro fichero:
CEMT I FILE(FILE1)


Ya tenemos el fichero listo para usar.

Antes de meternos a ver el programa, vamos a ver las instrucciones CICS de acceso a fichero que vamos a utilizar.

STARTBR: Apertura del fichero

      EXEC CICS STARTBR
                FILE ('FILE1')
                RIDFLD (WX-CODIGO)
                KEYLENGTH (WX-LONG-CLAVE)
                GTEQ
                RESP (WX-RESP)
      END-EXEC.


Donde:
FILE1 es el nombre que le hemos dado a nuestro fichero en CICS.
RIDFLD es el campo clave del fichero.
KEYLENGTH es la longitud de la clave.
GTEQ indica mayor o igual.
RESP indicamos la variable donde guardaremos el código de respuesta.

DELETE: borra un registro del fichero

    EXEC CICS DELETE
              RIDFLD (WX-CODIGO)
              FILE ('FILE1')
              NOHANDLE
    END-EXEC.


Nota: si usamos NOHANDLE el código de respuesta lo tendremos en EIBRESP


WRITE: graba un registro

     EXEC CICS WRITE
               FILE ('FILE1')
               RIDFLD (WX-CODIGO)
               LENGTH (WX-LONG-REG)
               FROM (REG-VSAMKSDS)
               NOHANDLE
     END-EXEC.

Donde:
LENGTH es la longitud del registro
FROM es el nivel 01 que define la estructura del fichero

READ: lectura directa

     EXEC CICS READ
               RIDFLD (WX-CODIGO)
               FILE ('FILE1')
               INTO (REG-VSAMKSDS)
               LENGTH (WX-LONG-REG)
               NOHANDLE
     END-EXEC.


Donde:
INTO es el nivel 01 que define la estructura del fichero.

READNEXT: lectura del fichero

     EXEC CICS READNEXT
               INTO (REG-VSAMKSDS)
               RIDFLD (WX-CODIGO)
               FILE ('FILE1')
               KEYLENGTH (WX-LONG-CLAVE)
               RESP (WX-RESP)
     END-EXEC.



UPDATE: indica lectura con intención de actualizar

     EXEC CICS READ
               RIDFLD (WX-CODIGO)
               FILE ('FILE1')
               INTO (REG-VSAMKSDS)
               LENGTH (WX-LONG-REG)
               UPDATE
               NOHANDLE
     END-EXEC.


Nota: con UPDATE bloqueamos el registro para que no se pueda acceder a él mientras tanto

REWRITE: actualiza un registro

     EXEC CICS REWRITE
               FILE ('FILE1')
               LENGTH (WX-LONG-REG)
               FROM (REG-VSAMKSDS)
               NOHANDLE
     END-EXEC.


ENDBR: cierre del fichero

     EXEC CICS ENDBR
               FILE ('FILE1')
               RESP (WX-RESP)
     END-EXEC.



La semana que viene continuaremos el artículo con el código completo del programa y su ejecución. Esta no hay tiempo para más!

lunes, 3 de enero de 2011

Programas con DB2 III: COUNT, MAX y FOR UPDATE.

Para terminar con la saga, vamos a ver estos tres últimos casos.

SELECT COUNT:
Esta sentencia nos recuperará la cantidad de registros que cumplen una condición, es decir, lo que devuelve es un número.

EXEC SQL
   SELECT COUNT(*)
     INTO :NUMERO-REGISTROS :IND-NULL
     FROM TABLA1
    WHERE CAMPO1 = 'XX'
END-EXEC

La variable IND-NULL la definiremos como S9(4) COMP y la utilizaremos para controlar el retorno, en caso de que no encuentre ningún registro que cumpla la condición.

Controlando el SQLCODE:
EVALUATE TRUE
   WHEN SQLCODE EQUAL ZEROES
      IF IND-NULL EQUAL -1
         MOVE ZEROES TO NUMERO-REGISTROS
      END-IF
   WHEN OTHER
      DISPLAY 'ERROR HACIENDO EL SELECT COUNT. SQLCODE: 'SQLCODE
END-EVALUATE

El indicador de nulos para el caso de no encontrar ningún registro valdrá -1. Si no controlásemos el indicador de nulos, la SELECT nos devolvería un SQLCODE -305.


SELECT MAX:
Esta sentencia nos recuperará el valor máximo de un campo para una determinada condición.

EXEC SQL
   SELECT MAX(CAMPO2)
     INTO :TABLA1-CAMPO2 :IND-NULL
     FROM TABLA1
    WHERE CAMPO1 = 01
END-EXEC

El control del retorno se hará del mismo modo que para el SELECT COUNT:
EVALUATE TRUE
   WHEN SQLCODE EQUAL ZEROES
      IF IND-NULL EQUAL -1
         MOVE ZEROES TO TABLA1-CAMPO2
      END-IF
   WHEN OTHER
      DISPLAY 'ERROR HACIENDO EL SELECT COUNT. SQLCODE: 'SQLCODE
END-EVALUATE


FOR UPDATE:
Esta sentencia se utiliza para actualizar el registro en el que nos hemos posicionado con un FETCH.

EXEC SQL
   DECLARE CUR-TABLA1 CURSOR FOR
    SELECT
          CAMPO1,
          CAMPO2,
          CAMPO3,
          CAMPO4
      FROM TABLA1
     WHERE CAMPO1 = :TABLA1-CAMPO1
     FOR UPDATE OF
          CAMPO2,
          CAMPO3,
          CAMPO4
END-EXEC

En el programa haríamos un update por cada FETCH del siguiente modo:
EXEC SQL
   UPDATE TABLA1
     SET CAMPO2 = :UPD-CAMPO2,
         CAMPO3 = :UPD-CAMPO3,
         CAMPO4 = :UPD-CAMPO4
    WHERE CURRENT OF CUR-TABLA1
END-EXEC

De este modo no tendríamos que indicarle la clave del registro a actualizar, pues actualizaría el registro abierto por el FETCH.

lunes, 13 de diciembre de 2010

Programas con DB2 I: SELECT, INSERT, UPDATE y DELETE.

En la mayoría de aplicaciones existirán, para cada tabla, una serie de rutinas con los diferentes tipos de accesos: consulta, lista, alta, modificación y baja.
Esto se traduce en SQL como: SELECT, CURSOR, INSERT, UPDATE y DELETE.
En este artículo vamos a "construir" cuatro de ellos; dejaremos la rutina de lista para explicar en un artículo aparte.

Este tipo de rutinas serán llamadas desde otros programas, ya sean BATCH u ONLINE. Tendrán definidas en la LINKAGE SECTION los campos a través de los cuales intercambiarán información con el programa llamante. Estos campos pueden venir definidos dentro de COPYs.

Consulta: SELECT.

Se trata del caso más sencillo en el que sólo queremos recuperar 1 registro de la tabla.

Vamos a ver un ejemplo:
LINKAGE SECTION
01 ENTRADA.
   05 CLAVE-SELECT PIC X(10).
01 SALIDA.
   05 SAL-CAMPO1 PIC X(10).
   05 SAL-CAMPO2 PIC X.
   05 SAL-PRI-CAMPO3 PIC XX.
   05 SAL-SEC-CAMPO3 PIC XX.

(...)
Los campos de entrada de la linkage contendrán la información enviada por el programa llamante. En este caso la clave por la que queremos acceder a nuestra SELECT.

En el inicio del programa podemos poner una validación del campo clave, para comprobar que trae datos, y en caso de no ser así finalizar la ejecución.
INICIO

IF CLAVE-SELECT EQUAL SPACES OR LOW-VALUES
   DISPLAY 'LA CLAVE VIENE VACIA'
   PERFORM 90000-FINAL
END-IF


Realizaremos la SELECT:

EXEC SQL
   SELECT CAMPO1,
          CAMPO2,
          PRI_CAMPO3,
          SEC_CAMPO3
     INTO :TABLA1-CAMPO1,
          :TABLA1-CAMPO2,
          :TABLA1-PRI-CAMPO3,
          :TABLA1-SEC-CAMPO3
     FROM TABLA1
    WHERE CAMPO1 = :CLAVE-SELECT
    ORDER BY CAMPO1
END-EXEC


Los campos que van en el apartado de SELECT son los campos de la tabla. Se escribirán del mismo modo en el que estén en el CREATE de la tabla. Se utilizará siempre el guión bajo.
Ocurre lo mismo en el apartado de ORDER BY.

Los campos que van en el apartado de INTO son variables HOST definidas en la WORKING del programa. Podemos nombrarlos como queramos. Siempre irán precedidos por los dos puntos ":". Se utilizará siempre el guión alto.
Ocurre lo mismo para los campos utilizados en el apartado de WHERE.
En caso de que tengamos campos numéricos, siempre irán definidos como signados y comprimidos (S9(X) COMP-3).

La variable HOST utilizada en el WHERE no deberá tener niveles inferiores. En caso de que así sea, deberemos utilizar un REDEFINES o moverla a otra variable:
01 CLAVE-SELECT.
   05 PARTE1 PIC X(5).
   05 PARTE2 PIC X(5).
01 CLAVE-ENTERA REDEFINES CLAVE-SELECT PIC X(10).

O bien:
01 CLAVE-SELECT.
   05 PARTE1 PIC X(5).
   05 PARTE2 PIC X(5).
01 CLAVE-ENTERA PIC X(10).
(...)
MOVE CLAVE-SELECT TO CLAVE-ENTERA


Puede darse el caso de que el campo del WHERE no sea una clave única de la tabla, y que la SELECT nos recupere más de un registro.
Si sólo queremos saber que existe "alguno" que cumple la condición, podemos solventar este problema añadiendo a la SELECT la sentencia FETCH FIRST ROW ONLY:

EXEC SQL
   SELECT CAMPO1,
          CAMPO2,
          PRI_CAMPO3,
          SEC_CAMPO3
     INTO :TABLA1-CAMPO1,
          :TABLA1-CAMPO2,
          :TABLA1-PRI-CAMPO3,
          :TABLA1-SEC-CAMPO3
     FROM TABLA1
    WHERE CAMPO1 = :CLAVE-SELECT
    ORDER BY CAMPO1
    FETCH FIRST ROW ONLY
END-EXEC


De este modo aunque recupere más de un registro no dará error, y nos devolverá el primero que encuentre.
Si no lo hiciésemos la consulta devolvería un SQLCODE = -811.

Una vez codificada la SELECT validaremos el código SQL que nos devuelve para comprobar que todo ha ido bien:

EVALUATE TRUE
   WHEN SQLCODE EQUAL ZEROES
      DISPLAY 'TODO VA BIEN'
   WHEN SQLCODE EQUAL +100
      DISPLAY 'NO ENCONTRE NADA'
   WHEN OTHER
      DISPLAY 'ALGO HA IDO MAL. EL SQLCODE ES: 'SQLCODE
      PERFORM 90000-FINAL
END-EVALUATE


Si la SELECT nos devuelve un SQLCODE distinto de cero y de 100, lo normal es finalizar la ejecución del programa.

Una vez comprobado que todo ha ido bien, informaremos los campos de SALIDA con la información recuperada de la SELECT:
MOVE TABLA1-CAMPO1     TO SAL-CAMPO1
MOVE TABLA1-CAMPO2     TO SAL-CAMPO2
MOVE TABLA1-PRI-CAMPO3 TO SAL-PRI-CAMPO3
MOVE TABLA1-SEC-CAMPO3 TO SAL-SEC-CAMPO3


En el párrafo de final devolveremos el control al programa llamante:
FINAL

GOBACK.


Y se terminó nuestra rutina de consulta : )

Alta: INSERT.

Cuando queremos dar de alta/insertar un registro en una tabla, tendremos que informar un mínimo de campos de la tabla(se deben informar al menos aquellos que pertenezcan a la clave única).
Estos campos vendrán informados desde el programa llamante y los recibiremos en la linkage.

Veamos un ejemplo:
LINKAGE SECTION.
01 ENTRADA.
   05 ENT-CAMPO1 PIC X(10).
   05 ENT-CAMPO2 PIC X.
   05 ENT-PRI-CAMPO3 PIC XX.
   05 ENT-SEC-CAMPO3 PIC XX.


En el inicio del programa podemos poner una validación de los campos obligatorios, para comprobar que vienen informados, y en caso de no ser así finalizar la ejecución.

Informaremos las variables HOST con las que realizaremos el INSERT:
MOVE ENT-CAMPO1     TO TABLA1-CAMPO1
MOVE ENT-CAMPO2     TO TABLA1-CAMPO2
MOVE ENT-PRI-CAMPO3 TO TABLA1-PRI-CAMPO3
MOVE ENT-SEC-CAMPO3 TO TABLA1-SEC-CAMPO3


Realizamos el INSERT:
EXEC SQL
   INSERT INTO TABLA1
         (CAMPO1,
          CAMPO2,
          PRI_CAMPO3,
          SEC_CAMPO3)
   VALUES(:TABLA1-CAMPO1,
          :TABLA1-CAMPO2,
          :TABLA1-PRI-CAMPO3,
          :TABLA1-SEC-CAMPO3)
END-EXEC


Es importante colocar los campos definidos en VALUES en el orden correcto.

Ahora validaremos el SQLCODE devuelto para comprobar que todo ha ido bien:
EVALUATE TRUE
   WHEN SQLCODE EQUAL ZEROES
      DISPLAY 'TODO HA IDO BIEN'
   WHEN SQLCODE EQUAL -803
      DISPLAY 'EL REGISTRO YA EXISTE EN LA TABLA'
   WHEN OTHER
      DISPLAY 'LA HEMOS LIAO PARDA. EL SQLCODE ES: 'SQLCODE
      PERFORM 90000-FINAL
END-EVALUATE


Una vez comprobado que todo ha ido bien, devolveremos el control al programa llamante:
FINAL
GOBACK.


Modificación: UPDATE.

Puede ocurrir que queramos modificar un solo campo o todos. Si no sabemos qué campos van a venir modificados lo normal es actualizarlos todos. Para ello debemos haber recuperado previamente toda la información del registro, o podríamos estar actualizando algunos campos con ceros o espacios.

En una rutina de modificación ONLINE se debe controlar siempre el TIMESTAMP de modificación del registro, para asegurarnos que no ha sido modificado mientras nosotros lo estábamos visualizando.

Veamos el ejemplo:
LINKAGE SECTION.
01 ENTRADA.
   05 ENT-CAMPO1 PIC X.
   05 ENT-CAMPO2 PIC X.
   05 ENT-CAMPO3 PIC XX.
   05 ENT-CAMPO4 PIC XX.
   05 ENT-TIMESTAMP PIC X(26).


La información de la linkage la habrá obtenido el programa llamante realizando una consulta del registro y con la información de los campos modificados.

Antes de realizar el UPDATE realizaremos una nueva consulta del registro:

EXEC SQL
   SELECT CAMPO1,
          TIMESTAMP
     INTO :TABLA2-CAMPO1,
          :TABLA2-TIMESTAMP
     FROM TABLA2
    WHERE CAMPO1 = :ENT-CAMPO1
    ORDER BY CAMPO1
END-EXEC


Validaremos el SQLCODE devuelto.
Si todo ha ido bien:
Compararemos el TIMESTAMP obtenido en TABLA2-TIMESTAMP con el recibido por linkage.
Recordad que un campo timestamp tiene este aspecto: 'AAAA-MM-DD-HH.MM.SS.MMMMMM'
Donde:
AAAA: año
MM: mes
DD: día
HH: hora
MM: minutos
SS: segundos
MMMMMM: milésimas de segundo

2010-12-01-22.15.01.012345

En total 26 posiciones.

IF ENT-TIMESTAMP NOT EQUAL TABLA2-TIMESTAMP
   DISPLAY 'ERROR: REGISTRO MODIFICADO POR OTRO USUARIO'
   PERFORM 90000-FINAL
END-IF


Esto significa que mientras nosotros visualizábamos el registro, otra persona lo modificó.
En caso de que los TIMESTAMP coincidan, informaremos las variables HOST del update con la información recibida por linkage.
MOVE ENT-CAMPO1 TO TABLA2-CAMPO1
MOVE ENT-CAMPO2 TO TABLA2-CAMPO2
MOVE ENT-CAMPO3 TO TABLA2-CAMPO3
MOVE ENT-CAMPO4 TO TABLA2-CAMPO4


Procederemos a hacer el UPDATE:

EXEC SQL
   UPDATE TABLA2
      SET (CAMPO2 = :TABLA2-CAMPO2,
           CAMPO3 = :TABLA2-CAMPO3,
           CAMPO4 = :TABLA2-CAMPO4,
           TIMESTAMP = CURRENT TIMESTAMP)
    WHERE CAMPO1 = :TABLA2-CAMPO1
END-EXEC


Tened en cuenta que no se deben modificar los campos de la tabla que pertenezcan a la clave única.

Validaremos el SQLCODE devuelto.
EVALUATE TRUE
   WHEN SQLCODE EQUAL ZEROES
      DISPLAY 'TODO HA IDO BIEN'
   WHEN SQLCODE EQUAL +100
      DISPLAY 'EL REGISTRO QUE SE QUIERE MODIFICAR NO EXISTE'
   WHEN OTHER
      DISPLAY 'YA LA HEMOS VUELTO A LIAR. EL SQLCODE ES: 'SQLCODE
      PERFORM 90000-FINAL
END-EVALUATE


Y devolveremos el control al programa llamante.

Puede darse el caso de que queramos modificar algún campo de la clave del registro. Normalmente para que pueda hacerse debe existir un campo clave o ISN a mayores que será numérico y autoincrementable y que identificará inequívocamente cada registro de la tabla.
Por ejemplo:


Donde CAMPO1 será el índice único y CLAVE la clave autoincrementable.
Ahora podríamos modificar el campo CAMPO1 accediendo a la tabla por el campo CLAVE.
Por ejemplo, si queremos modificar el campo CAMPO1 del registro con CLAVE = 14:
MOVE '5'  TO CAMPO1
MOVE 'X'  TO CAMPO2
MOVE 'YY' TO CAMPO3
MOVE 'ZZ' TO CAMPO4

EXEC SQL
   UPDATE TABLA2
     SET (CAMPO1 = :TABLA2-CAMPO1,
          CAMPO2 = :TABLA2-CAMPO2,
          CAMPO3 = :TABLA2-CAMPO3,
          CAMPO4 = :TABLA2-CAMPO4,
          TIMESTAMP = CURRENT TIMESTAMP)
    WHERE CLAVE = :TABLA2-CLAVE
END-EXEC


y el registro con CLAVE =14 quedaría:


Baja: DELETE.

Este tipo de baja también se denomina "baja física", en contraste con la "baja lógica", donde el registro no se borra, sino que se actualiza un campo "estado" indicando que el registro en cuestión "está de baja" ^^.

Para realizar un DELETE sólo necesitaremos el valor del campo clave del registro que queremos eliminar.

LINKAGE SECTION
01 ENTRADA.
   05 CAMPO-CLAVE PIC X.
(...)

EXEC SQL
   DELETE FROM TABLA3
    WHERE CAMPO1 = :CAMPO-CLAVE
END-EXEC


Validaremos el SQLCODE devuelto y devolveremos el control al programa llamante.

Os dejo los enlaces a los ejemplos completos de SELECT, INSERT, UPDATE y DELETE.