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

miércoles, 13 de septiembre de 2023

Curiosidades COBOL: los límites del compilador.

DEPRECATED!

Con las nuevas versiones de COBOL, como COBOL 6, las cosas han cambiado!


----------------------------------------------------------------------------------------------------------------------

Normalmente un programa no suele alcanzar los límites del compilador (a no ser que seamos muy bestias^^), pero existir, existen.

Alguna vez os habéis preguntado cual es el máxino número de OCCURS que puede tener una tabla interna??? Vamos a ver algunos números curiosos : )

Líneas de un programa:
El nº máximo de líneas que puede tener un programa son 999.999 líneas. Sinceramente, encontrarse un programa así de largo haría que nos acordásemos de toda la familia del que tuvo la idea...

En dos palabras, in-mantenible^^

Ficheros:
El nº máximo de ficheros por programa es de 65.535 ficheros.
Y si tal que haya que cruzarlos!!!

Longitud de fichero:
La longitud máxima que puede tener un fichero en su RECORD es de 1.048.575 posiciones.
Total na.

Variables de working:
La suma de las variables definidas en la WORKING-STORAGE, no podrá pasar de 134.217.727. Lo mismo para la LINKAGE-SECTION.

EVALUATE:
El nº máximo de cláusulas WHEN en un EVALUATE será de 256.
Si que son posibilidades!

PERFORM:
A lo largo del programa sólo podremos utilizar 4.194.303.
Debe ser por si queremos codifcar el programa de 999.999 líneas...

OCCURS:
El número máximo de occurs que podrá tener una tabla interna será de 16.777.215.

USING en la procedure:
Cuando tengamos un programa con LINKAGE SECTION, el máximo de variables que podremos incluir en la PROCEDURE DIVISION USING ... será de 32.767.

Cláusula VALUE:
El total de las longitudes de los valorse incluidos en las cláusulas VALUE no podrá superar los 16.777.215.


Todo esto y mucho más lo podéis encontrar en el apéndice B del manual de IBM
Enterprise COBOL for z/OS and OS/390 IBM
Language Reference

miércoles, 19 de abril de 2017

PROCEDURE DIVISION: proceso del programa.

Actualizado: incluimos EVALUATE
La PROCEDURE DIVISION es la parte del programa donde se codifica el proceso en sí. Es decir, aquí escribiremos las sentencias cobol para llevar a cabo la función del programa.

INFORMANDO VARIABLES

En cobol lo que se hace es "mover" la información de una variable a otra utilizando la sentencia MOVE.
A la hora de mover información tendremos que tener en cuenta 2 cosas:
  • Formato(PIC) de ambas variables
  • Longitud de ambas variables

Ejemplo 1.
01 WX-TELEFONO   PIC X(9) VALUE '666111333'.
01 WX-TELEFONO-2 PIC X(9).


Tienen mismo formato y misma longitud, por lo que no habrá problemas. La sentencia sería:
MOVE WX-TELEFONO TO WX-TELEFONO-2

Ahora WX-TELEFONO-2 tendrá el valor '666111333'.

Ejemplo 2.
01 WX-TELEFONO     PIC X(9) VALUE '666111333'.
01 WX-TELEFONO-NUM PIC 9(9).


Ahora tenemos misma longitud pero distinto formato. En este caso la variable alfanumérica sólo tiene números, por lo que no habrá problema.

MOVE WX-TELEFONO TO WX-TELEFONO-NUM
Ahora WX-TELEFONO-NUM tendrá el valor 666111333.

Ejemplo 3.
01 WX-TELEFONO     PIC X(9) VALUE 'AAA111333'.
01 WX-TELEFONO-NUM PIC 9(9).


Ahora tenemos caracteres alfabéticos en la variable alfanumérica, esto no dará error, aunque no tiene mucho sentido hacerlo.

Dependiendo de la instalación, este tipo de movimientos donde pasamos caracteres alfabéticos a campos numéricos darán, o no, un error en ejecución: el MOVE dará un estupendo "casque" al ejecutar con código error S0C7. Lo mismo ocurre si lo movemos a un campo COMP-3 o COMP.
En cualquier caso, NO debemos mover caracteres alfabéticos a un campo numérico, COMP o COMP-3.

Ejemplo 4.
01 WX-TELEFONO     PIC X(9) VALUE '666111333'.
01 WX-TELEFONO-NUM PIC 9(6).


En este caso no coinciden ni las longitudes ni los formatos. Como el campo alfanumérico sólo lleva números no habrá problema, pero al no coincidir las longitudes el campo se "truncará".

MOVE WX-TELEFONO TO WX-TELEFONO-NUM
Ahora el campo WX-TELEFONO-NUM tendrá valor 111333. Hemos perdido los 3 primeros dígitos.

Si fuese al revés:
01 WX-TELEFONO-NUM PIC 9(9) VALUE 666111333.
01 WX-TELEFONO     PIC X(6).

MOVE WX-TELEFONO-NUM TO WX-TELEFONO

El valor del campo WX-TELEFONO sería 666111.

Cuando se mueve a un campo alfanumérico, el movimiento se hace de la posición 1 del campo inicial a la posición 1 del campo alfanumérico.
Cuando se mueve a un campo numérico, el movimiento se hace de la última posición del campo inicial a la última posición del campo numérico.

Ejemplo 5.
01 WX-NUMERO-ALF PIC X(4) VALUE '12 '.
01 WX-NUMERO     PIC 9(4).


Puede darse el caso de que valores numéricos nos vengan con espacios en lugar de ceros a la izquierda.
Una manera de solventar esto es utilizando el COMPUTE FUNCTION NUMVAL:

COMPUTE WX-NUMERO = FUNCTION NUMVAL(WX-NUMERO-ALF)


Ahora la variable WX-NUMERO tendrá el valor 0012. Es una manera de asegurarnos de que no casque el programa porque algún vago no haya escrito los ceros^^
El movimiento en sí no daría error, pero no debe hacerse.

Un ejemplo más claro:
01 WX-NUMERO-ALF PIC X(4) VALUE '12,00  '.
01 WX-NUMERO     PIC 9(4)V9(2).


Con el COMPUTE FUNCTION NUMVAL conseguiríamos que WX-NUMERO tuviese el valor  00120{ (aplantillado con la coma decimal).

Campos COMP-3 y COMP.
Ejemplo 6.
01 WX-SUELDO-COMP3 PIC S9(6) COMP-3 VALUE 100000.
01 WX-SUELDO-NUM   PIC 9(6).

MOVE WX-SUELDO-COMP3 TO WX-SUELDO-NUM

Sin problema. WX-SUELDO-NUM valdrá 100000.

Ejemplo 7.
01 WX-SUELDO-COMP3 PIC S9(6) COMP-3 VALUE 100000.
01 WX-SUELDO-ALF   PIC X(6).

MOVE WX-SUELDO-COMP3 TO WX-SUELDO-ALF

Sin problema. WX-SUELDO-ALF valdrá 100000.

Ejemplo 8.
01 WX-SUELDO-COMP PIC S9(6) COMP VALUE 100000.
01 WX-SUELDO-NUM  PIC 9(6).

MOVE WX-SUELDO-COMP TO WX-SUELDO-NUM

Sin problema. WX-SUELDO-ALF valdrá 100000.

Ejemplo 9.
01 WX-SUELDO-COMP PIC S9(6) COMP VALUE 100000.
01 WX-SUELDO-ALF  PIC X(6).

MOVE WX-SUELDO-COMP TO WX-SUELDO-ALF

Sin problema. WX-SUELDO-ALF valdrá 100000.

Ejemplo 10.
01 WX-SUELDO-COMP3 PIC S9(6) COMP-3 VALUE 100000.
01 WX-SUELDO-COMP  PIC 9(6) COMP.

MOVE WX-SUELDO-COMP3 TO WX-SUELDO-COMP

Sin problema, WX-SUELDO-COMP valdrá 100000 pero en formato hexadecimal: 186A0

Ejemplo 11.
01 WX-SUELDO-COMP  PIC 9(6) COMP VALUE 100000.
01 WX-SUELDO-COMP3 PIC S9(6) COMP-3.

MOVE WX-SUELDO-COMP TO WX-SUELDO-COMP3

Sin problema, WX-SUELDO-COMP valdrá 100000.

Para campos numéricos existe también la sentencia COMPUTE para pasar información de uno a otro:
Ejemplo 12.
01 WX-NUMERO-1 PIC 9(5) VALUE 12345.
01 WX-NUMERO-2 PIC 9(5).

COMPUTE WX-NUMERO-2 = WX-NUMERO-1

Ahora WX-NUMERO-2 valdrá 12345.

Aunque la sentencia COMPUTE se usa en general para operaciones aritméticas: suma, resta, multiplicación, división, potencia...

COMPUTE WX-NUMERO1 =
((WX-NUMERO2 + WX-NUMERO3) - (WX-NUMERO4 * WX-NUMERO5) / WX-NUMERO6) ** 2


Y se aplican las mismas reglas de prioridades que en matemáticas.(el doble asterisco es la potencia)

Es en estos casos cuando un campo numérico que lleve caracteres o espacios pierde su sentido, pues adivinad que sucede si intentamos dividir 'ABC?%&' entre 1000 al ejecutar el programa...
Premio! Un S0C7!!


Nunca va a tener sentido dividor letras entre números, imagino que en esto estamos todos de acuerdo : P

Para operaciones aritméticas existen también:
ADD: operador suma. "ADD 1 TO WX-CAMPO"
SUBTRACT: operador resta. "SUBTRACT 1 FROM WX-CAMPO"
MULTIPLY: operador multiplicación. "MULTIPLY WX-CAMPO BY 1"
DIVIDE: operador división. "DIVIDE WX-CAMPO BY 1"
REMAINDER: es el resto de una división. "DIVIDE WX-CAMPO BY 1 REMAINDER WX-RESTO"


Ejemplo 13.
01 WX-TELEFONO-NUM PIC 9(9) VALUE  666111333.
01 WX-TELEFONO-ALF PIC X(9) VALUE '666111333'.
01 WX-TELEFONO     PIC X(6).


Para campos alfanuméricos y numéricos sin comprimir existe la posibilidad de mover sólo determinadas posiciones del campo.

MOVE WX-TELEFONO-NUM(1:6) TO WX-TELEFONO

En este caso estamos cogiendo 6 posiciones, empezando desde la posición 1. Ahora WX-TELEFONO valdrá '666111'.

MOVE WX-TELEFONO-ALF(4:) TO WX-TELEFONO

En este caso estamos cogiendo todas las posiciones hasta final de campo desde la posición 4. Ahora WX-TELEFONO valdrá '111333'.

Para mover posiciones de un campo que pertenece a un array o tabla, primero habrá que indicar el occurs o índice y después la posición:
01 WX-TABLA OCCURS 5 TIMES.
   05 WX-TELEFONO1 PIC X(9) VALUE '111111111'.
      05 WX-TELEFONO2 PIC X(9) VALUE '222222222'.
   05 WX-TELEFONO3 PIC X(9) VALUE '333333333'.
   05 WX-TELEFONO4 PIC X(9) VALUE '444444444'. 

MOVE WX-TELEFONO3(3)(4:) TO WX-TELEFONO

Ahora WX-TELEFONO valdrá '333333'.


CONCATENANDO CAMPOS

En COBOL también existe la opción de concatenar campos usando la sentencia STRING. Lo que haremos será indicar las variables a concatenar, el modo en que se unirán y el campo donde se guardará el resultado.

01 WX-CAMPO1    PIC X(17) VALUE 'CONSULTORIO COBOL'.
01 WX-CAMPO2    PIC X(10) VALUE ' SON GUAYS'.
01 WX-RESULTADO PIC X(41).

STRING 'LOS TIPOS DEL ' WX-CAMPO1 WX-CAMPO2
DELIMITED BY SIZE
INTO WX-RESULTADO





Donde DELIMITED BY SIZE indica que los campos se unirán según el tamaño que ocupen, y el INTO indicará el campo donde guardar la información concatenada.
Si por ejemplo tuviésemos campos con espacios al final podríamos indicarle un DELIMITED BY SPACES, para que cortase la información al encontrarse el primer espacio. Vamos a ver el ejemplo:

01 WX-DIA   PIC 9(2) VALUE 31.
01 WX-MES   PIC X(10).
01 WX-FECHA PIC X(19).

MOVE 'MAYO' TO WX-MES

Ahora WX-MES valdrá 'MAYO      '.

STRING 'HOY ES ' WX-DIA ' DE ' DELIMITED BY SIZE
WX-MES DELIMITED BY SPACE
'.' DELIMITED BY SIZE
INTO WX-FECHA


El resultado:
HOY ES 31 DE MAYO.


Si no hubiésemos indicado el DELIMITED BY SPACE, el resultado sería:
HOY ES 31 DE MAYO      .


Recordad: el DELIMITED BY se aplica a todos los campos que lo preceden. Usad el DELIMITED BY correcto en cada caso!

INICIALIZANDO CAMPOS

Para "vaciar" un campo podríamos moverle ceros o espacios (según su formato). Pero
en cobol existe una instrucción más sencilla que es el INITIALIZE.

01 WX-NUMERO PIC 9(5) VALUE 12345.
(...)
INITIALIZE WX-NUMERO


Ahora WX-NUMERO valdrá 00000. Si fuese alfanumérico valdría '     ' (cinco espacios).

Las variables "vacías" en cobol pueden tener 3 tipos de información:
ceros: 00000
espacios: '     '
low-values: '.....'

Por eso a la hora de preguntar si un campo está vacío en cobol preguntaremos:
IF WX-CAMPO EQUAL ZEROES OR SPACES OR LOW-VALUES
(...)


Nota: Tanto LOW-VALUES como HIGH-VALUES son palabras reservadas en cobol, y se pueden definir como "el valor más bajo posible" y "el valor más alto posible" respectivamente. Veremos su uso en otros artículos.


BUCLES Y CONDICIONES

Las condiciones en cobol se escriben de la siguiente manera:
IF WX-CAMPO = 1
   MOVE 1        TO WX-CAMPO2
ELSE
   MOVE WX-CAMPO TO WX-CAMPO2
END-IF


Podemos escribir IFs dentro de IFs (IFs anidados) y La sentencia ELSE no es obligatoria:

IF WX-CAMPO1 = 1
   IF WX-CAMPO2 = 2
      MOVE WX-CAMPO1 TO WX-CAMPO3
   END-IF
ELSE
   MOVE 1 TO WX-CAMPO3
END-IF

No tienen más ciencia que cualquier IF de cualquier otro lenguaje.

Los operadores lógicos serán OR, AND, EQUAL, NOT EQUAL.
Los operadores de mayor, menor, serán GREATER y LESS.
También se pueden utilizar combinaciones como GREATER OR EQUAL (mayor o igual), y también con símbolos: >= (mayor o igual), <= (menor o igual).

Otra forma de preguntar por una condición es con un EVALUATE.
Preguntando si se cumple una condición, es decir, si es TRUE:

EVALUATE TRUE
  WHEN WS-CAMPO1 EQUAL WS-CAMPO2
      (instrucciones a realizar)
  WHEN WS-CAMPO1 NOT EQUAL WS-CAMPO2
      (instrucciones a realizar)
  WHEN OTHER
      (instrucciones a realizar)
END-EVALUATE

Preguntando por el valor de un campo:
EVALUATE WS-CAMPO1
  WHEN 1
      (instrucciones a realizar) 
  WHEN 2
      (instrucciones a realizar) 
  WHEN OTHER
      (instrucciones a realizar) 
END-EVALUATE

Preguntando por el valor de varios campos:
EVALUATE WS-CAMPO1 ALSO WS-CAMPO2
  WHEN 1 ALSO 1
      (instrucciones a realizar) 
  WHEN 2 ALSO 2
      (instrucciones a realizar) 
  WHEN OTHER
      (instrucciones a realizar) 
END-EVALUATE

ó

EVALUATE TRUE ALSO TRUE
  WHEN WS-CAMPO1 = 1 ALSO WS-CAMPO2 = 1
      (instrucciones a realizar) 
  WHEN WS-CAMPO1 = 2 ALSO WS-CAMPO2 = 2
      (instrucciones a realizar) 
  WHEN OTHER
      (instrucciones a realizar) 
END-EVALUATE 

ó

EVALUATE WS-CAMPO1 ALSO TRUE
  WHEN 1 ALSO WS-CAMPO2 = 1
      (instrucciones a realizar) 
  WHEN 2 ALSO WS-CAMPO2 = 2
      (instrucciones a realizar) 
  WHEN OTHER
      (instrucciones a realizar) 
END-EVALUATE 

Los bucles son diferentes debido a que en cobol no existe la sentencia "while" de otros lenguajes. Aquí los bucles son siempre "UNTIL", es decir, hasta que se cumpla la condición especificada.

MOVE 0 TO WX-CAMPO

PERFORM UNTIL WX-CAMPO = 5 -> aquí es donde pregunta por la condición
   proceso a realizar
   ADD 1 TO WX-CAMPO
END-PERFORM


El proceso se realizará para:
WX-CAMPO = 0
WX-CAMPO = 1
WX-CAMPO = 2
WX-CAMPO = 3
WX-CAMPO = 4

MOVE 5 TO WX-CAMPO

PERFORM UNTIL WX-CAMPO = 5 -> aquí es donde pregunta por la condición
   proceso a realizar
END-PERFORM


El proceso no se llega a realizar.

Si quisiésemos preguntar por la condición al final del bucle, tendríamos que codificar el bucle con un "WITH TEST AFTER":

MOVE 0 TO WX-CAMPO


PERFORM WITH TEST AFTER
  UNTIL WX-CAMPO = 5
   proceso a realizar
   ADD 1 TO WX-CAMPO
END-PERFORM -> aquí es donde pregunta por la condición

El proceso se realizará para:
WX-CAMPO = 0
WX-CAMPO = 1
WX-CAMPO = 2
WX-CAMPO = 3
WX-CAMPO = 4

MOVE 5 TO WX-CAMPO

PERFORM WITH TEST AFTER
  UNTIL WX-CAMPO = 5
   proceso a realizar

   ADD 1 TO WX-CAMPO
END-PERFORM -> aquí es donde pregunta por la condición

El proceso se realiza una vez, antes de comprobar si se cumple la codición.

Como veíamos en la estructura general de un programa cobol, un párrafo entero también se puede repetir hasta que se cumpla una condición:

PERFORM 2000-PROCESO
  UNTIL CUMPLA-CONDICION

Otro tipo de bucles son los "PERFORM VARYING". En este caso tendremos un contador que se irá incrementando automáticamente según le indiquemos:

PERFORM VARYING WI-INDICE FROM 1 BY 1 UNTIL WI-INDICE = 10
  código a ejecutar
END-PERFORM

El "FROM" le indica desde que valor empezar. El "BY" le indica cuanto le ha de sumar cada vez.
Como en el otro caso, el "UNTIL" le indicará la condición de final de bucle.

Con todo esto, ya podemos empezar a ver un ejemplo de un programa sencillo : )

lunes, 10 de octubre de 2011

Ejemplo 6: programa que llama a otro programa

En este ejemplo vamos a ver un programa que llama a otro programa, denominado rutina, para recuperar información.
Se trata de un programa sin DB2 que recibirá un número de DNI por SYSIN y llamará a una rutina para calcular la letra de dicho NIF. La información recuperada la mostrará por SYSOUT.

JCL:

//PROG6 EXEC PGM=PRUEBA6
//SYSOUT DD SYSOUT=*
//SYSIN DD *
32684930
/*


donde EXEC PGM= indica el programa SIN DB2 que vamos a ejecutar
SYSOUT DD SYSOUT=* indica que la información "displayada" se quedará en la cola del SYSOUT (no lo vamos a guardar en un fichero)
en SYSIN DD * metemos la información que va a recibir el programa


PROGRAMA:

 IDENTIFICATION DIVISION.
 PROGRAM-ID. PRUEBA6.
*==========================================================*
*     PROGRAMA QUE LLAMA A OTRO PROGRAMA (RUTINA)
*==========================================================*
*
 ENVIRONMENT DIVISION.
*
 CONFIGURATION SECTION.
*
 SPECIAL-NAMES.
     DECIMAL-POINT IS COMMA.
*
 DATA DIVISION.
*
 WORKING-STORAGE SECTION.
*

 01 WX-SYSIN.
    05 WX-NUMERO-NIF     PIC X(8).
    05 FILLER            PIC X(72).

 01 WX-RUTINA.
    05 WX-NIF-COMPLETO.
       10 WX-NUMERO-NIF  PIC 9(8).
       10 WX-LETRA-NIF   PIC X.
    05 WX-RETORNO        PIC X(2).
 01 RUTINA1              PIC X(7) VALUE 'RUTINA1'.

*
************************************************************
 PROCEDURE DIVISION.
************************************************************
*  |     00000 - PRINCIPAL
*--|------------------+----------><----------+-------------*
* 1| EJECUTA EL INICIO DEL PROGRAMA
* 2| EJECUTA EL PROCESO DEL PROGRAMA
* 3| EJECUTA EL FINAL DEL PROGRAMA
************************************************************
 00000-PRINCIPAL.
*
     PERFORM 10000-INICIO
*
     PERFORM 20000-PROCESO
*
     PERFORM 30000-FINAL
     .

*
************************************************************
*  |     10000 - INICIO
*--|------------+----------><----------+-------------------*
*  | SE REALIZA EL TRATAMIENTO DE INICIO:
* 1| Inicialización de Áreas de Trabajo
* 2| Lectura de SYSIN
************************************************************
 10000-INICIO.
*
     INITIALIZE WX-SYSIN

                WX-RUTINA

     ACCEPT WX-SYSIN FROM SYSIN

     .
************************************************************
*  |     20000 - PROCESO
*--|------------------+----------><------------------------*
*  | SE REALIZA EL TRATAMIENTO DE LOS DATOS:
* 1| Realiza el acceso a base de datos
************************************************************
 20000-PROCESO.
*

     MOVE WX-NUMERO-NIF OF WX-SYSIN 
       TO WX-NUMERO-NIF OF WX-RUTINA

     CALL RUTINA1 USING WX-RUTINA  

     IF WX-RETORNO EQUAL 'OK'

        DISPLAY 'LA LLAMADA HA IDO BIEN'
        PERFORM 21000-GRABAR-SALIDA
     ELSE
        DISPLAY 'LA LLAMADA HA IDO MAL'
        PERFORM 30000-FINAL
     END-IF
     .

*************************************************************
*  |     21000 - GRABAR - SALIDA
*--|------------------+----------><----------+--------------*
*  | ESCRIBE EN SYSOUT LA INFORMACIÓN RECUPERADA DE LA TABLA
*************************************************************
  21000-GRABAR-SALIDA.
*

     DISPLAY 'NIF COMPLETO:'WX-NIF-COMPLETO

     .

*
************************************************************
*  |     30000 - FINAL
*--|------------------+----------><----------+-------------*
*  | FINALIZA LA EJECUCION DEL PROGRAMA
************************************************************
 30000-FINAL.
*
     STOP RUN
     .


En el programa podemos ver las siguientes divisiones/secciones:
IDENTIFICATION DIVISION: existirá siempre.
ENVIRONMENT DIVISION: existirá siempre.
  CONFIGURATION SECTION: existirá siempre.
  En este caso no existirá la INPUT-OUTPUT SECTION, pues nuestro programa no utiliza ficheros.
DATA DIVISION: existirá siempre.
  En este caso no existirá la FILE SECTION, pues nuestro programa no utiliza ficheros.
  WORKING-STORAGE SECTION: exisitirá siempre.
  En este caso no exisistirá la LINKAGE SECTION pues el programa no es llamado desde otros programas.
PROCEDURE DIVISION: exisitirá siempre.


En el programa podemos ver las siguientes sentencias:
PERFORM: llamada a párrafo
INITIALIZE: para inicializar variable
ACCEPT: esta sentencia recoge la información del campo indicado en el "FROM". En este caso recoge la información almacenada en "SYSIN"; la que nosotros hemos introducido en el JCL.
MOVE/TO: movemos la información de un campo a otro
CALL/USING:es la sentencia que usamos para llamar a una rutina. Después del CALL indicamos el nombre de la rutina que vamos a invocar, y después del USING indicamos las variables de comunicación entre ambos programas.
DISPLAY: escribe el contenido del campo indicado en la SYSOUT del JCL.
IF/ELSE: comprueba si se cumple una condición.
STOP RUN: sentencia de finalización de ejecución.


Descripción del programa:
En el párrafo de inicio, inicializamos las variables que vamos a utilizar a lo largo del programa. Luego mediante un ACCEPT recogemos la información que hemos escrito en la SYSIN de nuestro JOB.

En el párrafo de proceso, informamos el campo WX-NUMERO-NIF del área WX-RUTINA con la información recogida de SYSIN.
Como veis existen dos variables con el mismo nombre. Esto no dará problemas al compilar, mientras las variables pertenezcan a niveles superiores diferentes.
En nuestro caso tenemos un WX-NUMERO-NIF que pertenece a WX-SYSIN, y otro que pertenece a WX-RUTINA. Para utilizar estas variables a lo largo del programa tendremos que indicar a cual de ellas nos referimos, por eso les hemos añadido el "OF WX-XXXXX".

Una vez informada el área de comunicación entre dos programas, procedemos a hacer la llamada en sí con la sentencia CALL/USING.
Se trata de una llamada dinámica, pues el nombre de la rutina está contenido dentro de una variable, así que después de la llamada la rutina será descargada de la memoria.
En las llamadas estáticas el nombre de la rutina se indica entre comillas simpes 'RUTINA1'. En este caso, después de la llamada el módulo queda residente en memoria, porque se integra en el programa objeto.

Para que no haya errores comprobamos que la llamada ha ido bien validando el retorno (informado dentro de la rutina).
Si todo ha ido bien grabamos la información recuperada (NIF con letra) en la SYSOUT mediante un DISPLAY.


RUTINA

 IDENTIFICATION DIVISION.
 PROGRAM-ID. RUTINA1.
*==========================================================*
*     RUTINA QUE CALCULA LA LETRA DE UN NIF
*==========================================================*
*
 ENVIRONMENT DIVISION.
*
 CONFIGURATION SECTION.
*
 SPECIAL-NAMES.
     DECIMAL-POINT IS COMMA.
*
 DATA DIVISION.
*
 WORKING-STORAGE SECTION.
*

 01 WI-INDICES.
    05  WI-IND                            PIC 9(2).
*
 01 WX-VARIABLES.
    05  WX-NIF-DIVID                      PIC 9(8).
    05  WX-NIF-MULTI                      PIC 9(8).
*

 01 WT-TABLAS.
    05 WT-NIF-TABLA                        PIC X(24) 
                              VALUE "TRWAGMYFPDXBNJZSQVHLCKET".
    05 WT-NIF-TABLA-R         REDEFINES    WT-NIF-TABLA.
       10 WT-LETRA-TABLA      OCCURS 24    PIC X.   

*
 LINKAGE SECTION.
*
 01 WX-RUTINA.
    05 WX-NIF-COMPLETO.
       10 WX-NUMERO-NIF  PIC 9(8).
       10 WX-LETRA-NIF   PIC X.
    05 WX-RETORNO        PIC X(2).
*
************************************************************
 PROCEDURE DIVISION USING WX-RUTINA.
************************************************************
*  |     00000 - PRINCIPAL
*--|------------------+----------><----------+-------------*
* 1| EJECUTA EL INICIO DEL PROGRAMA
* 2| EJECUTA EL PROCESO DEL PROGRAMA
* 3| EJECUTA EL FINAL DEL PROGRAMA
************************************************************
 00000-PRINCIPAL.
*
     PERFORM 10000-INICIO
*
     PERFORM 20000-PROCESO
*
     PERFORM 30000-FINAL
     .
************************************************************
*  |     10000 - INICIO
*--|------------+----------><----------+-------------------*
*  | SE REALIZA EL TRATAMIENTO DE INICIO:
* 1| Inicialización de Áreas de Trabajo
************************************************************
 10000-INICIO.
*

     INITIALIZE WX-VARIABLES 
                WI-INDICES
 
     .

*
************************************************************
*  |     20000 - PROCESO
*--|------------------+----------><------------------------*
*  | SE REALIZA EL TRATAMIENTO DE LOS DATOS:
* 1| Realiza el cálculo de la letra del NIF
************************************************************
 20000-PROCESO.
*
     COMPUTE WX-NIF-DIVID = WX-NUMERO-NIF  /  23
     COMPUTE WX-NIF-MULTI = WX-NIF-DIVID  *  23
     COMPUTE WI-IND       = WX-NUMERO-NIF - WX-NIF-MULTI


     ADD 1                       TO WI-IND


     MOVE WT-LETRA-TABLA(WI-IND) TO WX-LETRA-NIF


     MOVE 'OK'                   TO WX-RETORNO
     .

*
************************************************************
*  |     30000 - FINAL
*--|------------------+----------><----------+-------------*
*  | FINALIZA LA EJECUCION DEL PROGRAMA
************************************************************
 30000-FINAL.
*
     GOBACK
     .



En el programa podemos ver las siguientes divisiones/secciones:
IDENTIFICATION DIVISION: existirá siempre.
ENVIRONMENT DIVISION: existirá siempre.
CONFIGURATION SECTION: existirá siempre.
En este caso no existirá la INPUT-OUTPUT SECTION, pues nuestro programa no utiliza ficheros.
DATA DIVISION: existirá siempre.
En este caso no existirá la FILE SECTION, pues nuestro programa no utiliza ficheros.
WORKING-STORAGE SECTION: exisitirá siempre.
LINKAGE SECTION: en este caso sí existirá puesto que se trata de una rutina que es llamada por un programa principal.
PROCEDURE DIVISION: exisitirá siempre.


En el programa podemos ver las siguientes sentencias:
PERFORM: llamada a párrafo
INITIALIZE: para inicializar variable
COMPUTE: realiza cálculos numéricos
ADD: operador de adición (suma)
MOVE/TO: movemos la información de un campo a otro
GOBACK: sentencia de finalización de ejecución para rutinas. Devuelve el control al programa llamante.


Descripción del programa:
En la LINKAGE SECTION definimos el área de comunicación con el programa llamante (PRUEBA6), en este caso WX-RUTINA.
En el párrafo de inicio inicializamos las variables que vamos a utilizar a lo largo del programa.

En el párrafo de proceso hacemos los cálculos necesarios para saber qué letra se corresponde al número de NIF que hemos introducido e informamos con un 'OK' el código de retorno.
En caso de que se produzca un error antes de terminar el proceso, el código de retorno irá vacío, y podremos controlar esta diferencia en el programa llamante.

Una vez calculada la letra del NIF devolvemos el control al programa PRUEBA6 haciendo GOBACK.

RESULTADO:

NIF-COMPLETO: 32684930K




Diferencias entre ambos programas:
LINKAGE SECTION: sólo la rutina (programa que es llamado por otro) tiene variables definidas en esta sección.
PROCEDURE DIVISION: sólo la rutina lleva asociada el área de comunicación entre programas en la procedure, añadiéndole la sentencia USING.
30000-FINAL: el programa principal lleva un STOP RUN de finalización de ejecución, mientras que la rutina lleva un GOBACK para devolver el control al programa llamante (programa que ha hecho el CALL).

Si tenéis cualquier duda sobre el uso de rutinas, ya sabéis, preguntad lo que queráis!

lunes, 12 de septiembre de 2011

Ejemplo 5: programa con DB2

En este ejemplo vamos a ver un programa que accede a base de datos para recuperar información. Veremos un ejemplo sencillo en el que el programa recuperará de SYSIN la clave por la que accederá a la base de datos, y displayará la información recuperada por SYSOUT.

JCL:

//PASO01 EXEC PGM=IKJEFT01,DYNAMNBR=20
//SYSTSIN DD *
DSN SYSTEM(DSN1)
RUN PROGRAM(PRUEBA5) PLAN(PRUEBA5) -
LIB('LIBRERIA.DE.TU.INSTALACION')
//SYSOUT DD SYSOUT=*
//SYSIN DD *
PEREZ
/*


Donde EXEC PGM=IKJEFT01 ejecutará el programa que le indiquemos en el SYSTSIN.
El DSN SYSTEM dependerá de cada instalación y puede variar según el entorno (desarrollo, preproduccion, explotación...).
En PROGRAM indicaremos el programa con DB2 a ejecutar, y en PLAN el plan DB2 asociado.
La librería indicada en LIB es opcional.
En la SYSIN le pasaremos la clave por la que accederemos a la tabla. En este caso será el campo APELLIDO.

TABLA:

Nombre     Apellido   Telefono
CARMEN     PEREZ      666555444
JAVIER     LOPEZ      666999888
SARA       GARCIA     666222111


Donde:
El campo Nombre es un CHAR(10).
El campo Apellido es un CHAR(10).
El campo Telefono es un DECIMAL(9).

PROGRAMA:

 IDENTIFICATION DIVISION.
 PROGRAM-ID. PRUEBA5.
*==========================================================*
*     PROGRAMA QUE ACCEDE A BASE DE DATOS CON UNA SELECT
*==========================================================*
*
 ENVIRONMENT DIVISION.
*
 CONFIGURATION SECTION.
*
 SPECIAL-NAMES.
     DECIMAL-POINT IS COMMA.
*
 DATA DIVISION.
*
 WORKING-STORAGE SECTION.
*

 EXEC SQL
    INCLUDE SQLCA
 END-EXEC.

 EXEC SQL
    INCLUDE TABLA
 END-EXEC.

 01 WX-SYSIN.
    05 WX-CLAVE        PIC X(10).
    05 FILLER          PIC X(70).

 01 WX-TABLA.
    05 WX-NOMBRE       PIC X(10).
    05 WX-APELLIDO     PIC X(10).
    05 WX-TELEFONO     PIC S9(9) COMP-3.
 01 WX-VARIABLES.
    05 WX-TELEFONO-NUM PIC 9(9).
*
************************************************************
 PROCEDURE DIVISION.
************************************************************
*  |     00000 - PRINCIPAL
*--|------------------+----------><----------+-------------*
* 1| EJECUTA EL INICIO DEL PROGRAMA
* 2| EJECUTA EL PROCESO DEL PROGRAMA
* 3| EJECUTA EL FINAL DEL PROGRAMA
************************************************************
 00000-PRINCIPAL.
*
     PERFORM 10000-INICIO
*
     PERFORM 20000-PROCESO
*
     PERFORM 30000-FINAL
     .
************************************************************
*  |     10000 - INICIO
*--|------------+----------><----------+-------------------*
*  | SE REALIZA EL TRATAMIENTO DE INICIO:
* 1| Inicialización de Áreas de Trabajo
* 2| Lectura de SYSIN
************************************************************
 10000-INICIO.
*
     INITIALIZE WX-SYSIN

                WX-TABLA
                WX-VARIABLES

     ACCEPT WX-SYSIN FROM SYSIN

     .
************************************************************
*  |     20000 - PROCESO
*--|------------------+----------><------------------------*
*  | SE REALIZA EL TRATAMIENTO DE LOS DATOS:
* 1| Realiza el acceso a base de datos
************************************************************
 20000-PROCESO.
*

     EXEC SQL
        SELECT NOMBRE
              ,TELEFONO
          INTO :WX-NOMBRE
              ,:WX-TELEFONO 
          FROM TABLA
         WHERE APELLIDO = :WX-CLAVE
         ORDER BY APELLIDO
     END-EXEC

     EVALUATE TRUE
        WHEN SQLCODE EQUAL ZEROES
             DISPLAY 'TODO VA BIEN'

             PERFORM 21000-GRABAR-SALIDA

        WHEN SQLCODE EQUAL +100
             DISPLAY 'NO ENCONTRE NADA'


        WHEN OTHER
             DISPLAY 'ALGO HA IDO MAL. EL SQLCODE ES: 'SQLCODE
             PERFORM 30000-FINAL
     END-EVALUATE
    

     .

*************************************************************
*  |     21000 - GRABAR - SALIDA
*--|------------------+----------><----------+--------------*
*  | ESCRIBE EN SYSOUT LA INFORMACIÓN RECUPERADA DE LA TABLA
*************************************************************
  21000-GRABAR-SALIDA.
*

     MOVE WX-TELEFONO TO WX-TELEFONO-NUM
     MOVE WX-CLAVE    TO WX-APELLIDO

     DISPLAY 'NOMBRE  :'WX-NOMBRE

     DISPLAY 'APELLIDO:'WX-APELLIDO
     DISPLAY 'TELEFONO:'WX-TELEFONO-NUM

     .

************************************************************
*  |     30000 - FINAL
*--|------------------+----------><----------+-------------*
*  | FINALIZA LA EJECUCION DEL PROGRAMA
************************************************************
 30000-FINAL.
*
     STOP RUN
     .


En el programa podemos ver las siguientes divisiones/secciones:
IDENTIFICATION DIVISION: existirá siempre.
ENVIRONMENT DIVISION: existirá siempre.
CONFIGURATION SECTION: existirá siempre.
En este caso no existirá la INPUT-OUTPUT SECTION, pues nuestro programa no utiliza ficheros.
DATA DIVISION: existirá siempre.
En este caso no existirá la FILE SECTION, pues nuestro programa no utiliza ficheros.
WORKING-STORAGE SECTION: exisitirá siempre.
En este caso no exisistirá la LINKAGE SECTION pues el programa no se comunica con otros programas.
PROCEDURE DIVISION: exisitirá siempre.


En el programa podemos ver las siguientes sentencias:
PERFORM: llamada a párrafo
INITIALIZE: para inicializar variable
ACCEPT: esta sentencia recoge la información del campo indicado en el "FROM". En este caso recoge la información almacenada en "SYSIN"; la que nosotros hemos introducido en el JCL.
EXEC SQL/END-EXEC: son las etiquetas para incluir código SQL. Todas las sentencias DB2 se deben incluir entre estas etiquetas.
EVALUATE TRUE: Valida que se cumplan las condiciones indicadas en cada uno de los "WHEN".
MOVE/TO: movemos la información de un campo a otro
DISPLAY: escribe el contenido del campo indicado en la SYSOUT del JCL.
STOP RUN: sentencia de finalización de ejecución.


Descripción del programa:
En la WORKING-STORAGE hemos incluido la copy SQLCA (SQL communications area ó área de comunicación SQL), que es la que utiliza el gestor de base de datos para devolver la información de errores. La estructura es la siguiente:

01 SQLCA SYNC.
   05 SQLCAID PIC X(8) VALUE "SQLCA ".
   05 SQLCABC PIC S9(9) COMP-5 VALUE 136.
   05 SQLCODE PIC S9(9) COMP-5.
   05 SQLERRM.
   05 SQLERRP PIC X(8).
   05 SQLERRD OCCURS 6 TIMES PIC S9(9) COMP-5.
   05 SQLWARN.
      10 SQLWARN0 PIC X.
      10 SQLWARN1 PIC X.
      10 SQLWARN2 PIC X.
      10 SQLWARN3 PIC X.
      10 SQLWARN4 PIC X.
      10 SQLWARN5 PIC X.
      10 SQLWARN6 PIC X.
      10 SQLWARN7 PIC X.
      10 SQLWARN8 PIC X.
      10 SQLWARN9 PIC X.
      10 SQLWARNA PIC X.
   05 SQLSTATE PIC X(5).
*


Como veis incluye la variable SQLCODE, donde guardará el código de retorno que nos indicará si la consulta ha ido bien o no.
Hemos incluido también la DCLGEN de la tabla "TABLA" que contendrá los campos DB2 de la tabla (los que codificamos en la parte de la SELECT) y sus campos working y que tendrá algo de este estilo:

EXEC SQL  DECLARE TABLA TABLE
     (NOMBRE        CHAR(10)           NOT NULL,
      APELLIDO      CHAR(10)           NOT NULL,
      TELEFONO      DECIMAL(9)         NOT NULL)
END-EXEC.

01  DCLTABLA.
     10 TAB-NOMBRE        PIC X(10).
     10 TAB-APELLIDO      PIC X(10).
     10 TAB-TELEFONO      PIC S9(9) COMP-3.

En el párrafo de inicio, inicializamos todas las variables de trabajo definidas en la WORKING-STORAGE, y realizamos un ACCEPT para recoger la información que hemos puesto en la SYSIN. En nuestro caso se trata del campo clave por el que accederemos a nuestra tabla.

En el párrafo de proceso, hacemos una SELECT a la tabla con la condición APELLIDO = :WX-CLAVE. Donde WX-CLAVE contiene el apellido recogido por SYSIN. Comprobamos el SQLCODE que nos devuelve esta consulta:
Si el SQLCODE es cero, la consulta ha ido bien y se ha encontrado el apellido contenido en WX-CLAVE en nuestra tabla. En nuestro caso dará este SQLCODE, pues "PEREZ" está en la tabla.
Si el SQLCODE es 100, la consulta ha ido bien pero no ha encontrado el apellido "PEREZ" en nuestra tabla.
Si el SQLCODE es cualquier otro, la consulta ha ido mal y habrá que comprobar en la tabla de errores DB2 cuál ha sido el problema.

NOTA: el tema de la puntuación en sentencias SQL (es decir, poner o no poner un punto '.' al final de una sentencia DB2 dentro de la PROCEDURE DIVISION) dependerá de la versión del compilador.

En el párrafo de GRABAR-SALIDA, movemos el campo WX-TELEFONO a una variable numérica sin comprimir (WX-TELEFONO-NUM), para que al hacer el DISPLAY se vea correctamente. Luego displayamos los campos recuperados de la consulta.


RESULTADO:

NOMBRE  : CARMEN
APELLIDO: PEREZ
TELEFONO: 666555444


Si queréis probar este ejemplo, tendréis que hacer una sentencia SELECT a una tabla que exista en vuestra base de datos^^

miércoles, 7 de septiembre de 2011

Los libros de Consultorio Cobol

Bienvenidos a todos!
Las vacaciones de verano ya se han terminado, y en el Consultorio Cobol volvemos al trabajo.
En esta ocasión os traemos "Los libros del Consultorio Cobol", que consistirán en una recopilación de artículos de un determinado tema, en formato pdf. De esta forma tendréis toda la información en un sólo archivo, y podréis hacer búsquedas fácilmente.

Los libros los publicaremos a través de la web Lulu.com y podrán adquirirse por la módica cantidad de 1,50€. ¿Por qué esa cantidad y no gratis? Pues sobre todo para que los interesados nos echen una mano en mantener esto. No sólo nos servirá para pagar el dominio y el hosting, sino que también nos dará ánimos para seguir adelante : )

Hemos empezado publicando el libro "JCL Básico. Introducción al mundo del JCL o cómo hacer copy/paste con “sentidiño”." que ya tenéis disponible:



Esperamos que os guste y, sobre todo, que os sea útil.

Próximos lanzamientos: PGM=SORT: utilidades.

miércoles, 22 de junio de 2011

Ejemplo 1: Leer de SYSIN y escribir en SYSPRINT (pl/i).

En PL/I hay que diferenciar entre los programas que acceden a DB2 y los que no, pues se compilarán de maneras diferentes y se ejecutarán de forma diferente.
Enpezaremos por ver el programa sin DB2 más sencillo:

El programa más sencillo es aquel que recibe datos por SYSIN del JCL y los muestra por SYSPRINT.

JCL:

//PROG1 EXEC PGM=PRUEBA1
//SYSPRINT DD SYSOUT=*
//SYSIN DD *
JOSE LOPEZ VAZQUEZ  HUGO CASILLAS DIAZ
JAVIER CARBONERO    PACO GONZALEZ
JESUS IGLESIAS      RICARDO MONTES
/*


donde EXEC PGM= indica el programa SIN DB2 que vamos a ejecutar
SYSPRINT DD SYSOUT=* indica que la información "displayada" se quedará en la cola del SYSPRINT (no lo vamos a guardar en un fichero)
en SYSIN DD * metemos la información que va a recibir el programa

Fijaos en las posiciones de los nombres de la SYSIN, para entender bien el programa:

----+----1----+----2----+----3----+----4----+----5----+----6----+----7----+----8
JOSE LOPEZ VAZQUEZ  HUGO CASILLAS DIAZ
JAVIER CARBONERO    PACO GONZALEZ
JESUS IGLESIAS      RICARDO MONTES


Como veis, hemos dividido la información en 2 trozos de 20 posiciones, cada uno con un nombre. Además hemos escrito varias líneas de SYSIN, pues como vimos en el artículo de Ficheros en PL/I, podemos recuperar varias líneas (en COBOL esto no es posible).

PROGRAMA:

PLIPRU1: PROCEDURE OPTIONS (MAIN);
/* PROGRAMA QUE LEE DE SYSIN(GET EDIT)*/
/* Y ESCRIBE EN SYSPRINT (PUT EDIT) */
/*DEFINIMOS SYSIN*/
DCL SYSIN FILE STREAM INPUT;
/*DEFINIMOS SYSPRINT*/
DCL SYSPRINT FILE PRINT;
/*DECLARACION DE VARIABLES DEL PROGRAMA*/
DCL 1 TABLA_NOMBRES,
      2 NOMBRE(6) CHAR(20);
DCL 1 LINEA_SYSIN,
      2 NOMBRE_SYSIN1 CHAR(20),
      2 NOMBRE_SYSIN2 CHAR(20);
DCL CONT_NOMBRE DEC FIXED (2);
DCL FIN CHAR(1) INIT ('0');
/*CONTROL FIN SYSIN*/
ON ENDFILE(SYSIN) BEGIN;
   FIN = '1';
END;
/*PROCESO DEL PROGRAMA*/
GET FILE(SYSIN) EDIT(LINEA_SYSIN)(A(40));

CONT_NOMBRE = 1;

DO WHILE (FIN = '0');
   CALL MOVER_A_TABLA;

   GET FILE(SYSIN) EDIT(LINEA_SYSIN)(A(40));
END;

CALL PINTAR_NOMBRES;

/*PARRAFO QUE GUARDA LOS NOMBRES RECUPERADOS EN TABLA_NOMBRES*/
MOVER_A_TABLA: PROC;
NOMBRE(CONT_NOMBRE) = NOMBRE_SYSIN1;

CONT_NOMBRE = CONT_NOMBRE + 1;

NOMBRE(CONT_NOMBRE) = NOMBRE_SYSIN2;

CONT_NOMBRE = CONT_NOMBRE + 1;

END MOVER_A_TABLA;

/*PARRAFO QUE ESCRIBE EN EL SYSPRINT LOS NOMBRES DE LA SYSIN*/
PINTAR_NOMBRES: PROC;
CONT_NOMBRE = 1;

DO WHILE CONT_NOMBRE < 7 

   PUT EDIT('NOMBRE: ', NOMBRE(CONT_NOMBRE)) (SKIP,A,A); 

CONT_NOMBRE = CONT_NOMBRE + 1; 
END;

END PINTAR_NOMBRES; 
END PLIPRU1;


En el programa podemos ver las siguientes sentencias:
ON ENDFILE(SYSIN): controla el final de la SYSIN (como el fin fichero pero para la SYSIN).
GET FILE: lee del fichero STREAM SYSIN.
DO WHILE: es un bucle. Las instrucciones de dentro del bucle se harán MIENTRAS se cumpla la condición indicada.
CALL: llamada a párrafo
PUT EDIT: escribe en el fichero STREAM SYSPRINT.
SKIP: indica salto de línea.
END PLIPRU1: indica fin del programa.


Descripción del programa:
Al inicio, declararemos los ficheros y las variables que utilizaremos a lo largo del programa.
En el proceso principal, leemos el primer registro de la SYSIN y ponemos a 1 el contador CONT_NOMBRE y montamos un bucle para recuperar todos los registros.

En el párrafo MOVER_A_TABLA guardamos los 2 nombres recuperados de la SYSIN en diferentes filas de la tabla TABLA_NOMBRES. Para ello indicamos entre paréntesis el número de la fila donde se va a guardar. El máximo de filas de la tabla es 6 (es el número indicado entre paréntesis al lado del campo NOMBRE).

En el párrafo PINTAR_NOMBRES escribiremos en el SYSPRINT todos los nombres guardados en nuestra TABLA_NOMBRES.
Informamos el campo CONT_NOMBRE con un 1, pues vamos a utilizar los campos de la tabla interna:
Para utilizar un campo que pertenezca a una tabla interna, debemos acompañar el campo de un "índice" entre paréntesis. De tal forma que indiquemos a que fila de la tabla nos estamos refiriendo. Por ejemplo, NOMBRE(1) sería el primer nombre guardado (JOSE LOPEZ VAZQUEZ).
Como queremos displayar todas las filas de la tabla, haremos que el índice sea una variable que va aumentando.

A continuación montamos un bucle (DO WHILE) con la condición CONT_NOMBRE menor de 7, pues el máximo es 6. La primera vez CONT_NOMBRE valdrá 1, y necesitamos que el bucle se repita 6 veces:
CONT_NOMBRE = 1: NOMBRE(1) = JOSE LOPEZ VAZQUEZ
CONT_NOMBRE = 2: NOMBRE(2) = HUGO CASILLAS DIAZ
CONT_NOMBRE = 3: NOMBRE(3) = JAVIER CARBONERO
CONT_NOMBRE = 4: NOMBRE(4) = PACO GONZALEZ
CONT_NOMBRE = 5: NOMBRE(5) = JESUS IGLESIAS
CONT_NOMBRE = 6: NOMBRE(6) = RICARDO MONTES
CONT_NOMBRE = 7: salimos del bucle porque ya no se cumple CONT_NOMBRE menor que 7


NOTA: el índice de una tabla interna NUNCA puede ser cero, pues no existe la fila cero. Si no informásemos CONT_NOMBRE con 1, el PUT de NOMBRE(0) nos daría un estupendo OFFSET.

RESULTADO:
NOMBRE: JOSE LOPEZ VAZQUEZ
NOMBRE: HUGO CASILLAS DIAZ
NOMBRE: JAVIER CARBONERO
NOMBRE: PACO GONZALEZ
NOMBRE: JESUS IGLESIAS
NOMBRE: RICARDO MONTES


He de decir que no me ha dado tiempo a ejecutar este ejemplo, en cuanto tenga un rato lo pruebo y corrijo si es necesario.

miércoles, 15 de junio de 2011

El mapa de Consultorio Cobol

A modo de curiosidad, nos gustaría compartir con vosotros el mapa de visitas del Consultorio cobol.
Por raro que parezca, no sólo están los países de habla hispana, sino que hay un poco de todo. Algunos que directamente nos meten en el Google Translator. No está mal!

Vamos a ver primero el mapa del Consultorio Cobol en el mundo:


La lista de los 10 países que más nos visitan es:
  1. España
  2. México
  3. Argentina
  4. Estados Unidos
  5. Chile
  6. Perú
  7. Colombia
  8. India
  9. Venezuela
  10. Brasil

Y más en detalle el top 3 de países:

España


Top 10 ciudades para España:
  1. Madrid
  2. Vigo
  3. Ourense
  4. Salamanca
  5. Barcelona
  6. Alcobendas
  7. Málaga
  8. A Coruña
  9. Alicante
  10. Sevilla

México



Top 10 ciudades para México:
  1. Mexico D.F.
  2. Queretaro
  3. Toluca
  4. Monterrey
  5. Guadalajara
  6. Aguascalientes
  7. Poza Rica de Hidalgo
  8. Naucalpan
  9. Cuautitlan Izcalli
  10. León de Los Aldama

Argentina



Top 10 ciudades para Argentina:
  1. Buenos Aires
  2. Rosario
  3. Córdoba
  4. San Miguel de Tucumán
  5. Lomas de Zamora
  6. Morón
  7. Quilmes
  8. La Plata
  9. Avellaneda
  10. Santa Fe

Sólo queda decir: gracias por elegir Consultorio Cobol : )

lunes, 13 de junio de 2011

Ficheros en PL/I

Seguimos adentrándonos en el mundo del PL/I. En este artículo veremos los tipos de ficheros con los que se puede trabajar y como defenirlos. Los usos los iremos viendo en futuros ejemplos.

FICHEROS RECORD
Como vimos en artículos anteriores, un programa PL/I no tiene divisiones como el COBOL, por lo que podremos definir el fichero en cualquier punto del programa. Para poner orden, lo definiremos al principio del procedimiento principal.
Los ficheros de tipo RECORD deben declararse explícitamente.

PRUEBA1: PROCEDURE OPTIONS(MAIN);

DCL FICHERO1 FILE INPUT;


Donde FICHERO1 será el nombre lógico del fichero, que se corresponderá con el nombre que se le haya dado en el JOB.
FILE nos indica que se trata de un fichero.
INPUT es el atributo, y nos indica que se trata de un fichero de entrada. Otros posibles atributos son:
OUTPUT: fichero de salida.
UPDATE: fichero de entrada y salida (para modificaciones; DISP=MOD).
SEQUENTIAL: fichero secuencial.
DIRECT: acceso directo.
INDEXED: fichero indexado.
KEYED: para acceder al fichero se utilizarán claves.
ENVIROMENT: describe el conjunto de datos externos asociados al fichero (ej: ENV(VSAM)).
EXTERNAL: el fichero es un elemento externo al programa.
RECORD: fichero tipo record.


Apertura/Cierre
Forma explícita:
OPEN FILE (FICHERO1); / CLOSE FILE (FICHERO1);


Podemos añadir atributos a continuación del nombre, siempre que no se contradigan con lo declarado en la definición.

Forma implícita:
Si se ejecuta una instrucción de entrada/salida sin haber abierto el fichero con la sentencia OPEN, el sistema abrirá el fichero de forma implícita y se le asignarán los atributos que tenga en la declaración.
Si no hacemoe el CLOSE del fichero, el sistema lo cerrará al final de la ejecución.
OJO! En los ficheros de salida, importante hacer OPEN. Si por casuística no se escribe sobre ellos, el sistema intentará cerrarlos al final de la ejecución, y al no haberlos abierto previamente dará un error.

Lectura
Dependerá del tipo de acceso:
SEQUENTIAL(secuencial)
READ FILE (FICHERO1) INTO (REG_ENTRADA);


Donde REG_ENTRADA es la variable donde se guardará la información del registro.

DIRECT(directo)
READ FILE (FICHERO1) INTO (REG_ENTRADA) KEY(CLAVE);


Donde KEY indica que se accederá por clave, y CLAVE es una variable donde estará el valor de la clave por la que vamos a acceder.

Escritura
Dependerá del tipo de acceso.
SEQUENTIAL(secuencial)
WRITE FILE (FICHERO2) FROM (REG_SALIDA);


Donde REG_SALIDA es la variable que contiene la información que escribiremos en el fichero.

DIRECT(directo)
WRITE FILE (FICHERO2) FROM (REG_SALIDA) KEYFROM (CLAVE);


Donde KEYFROM indica que se accederá por clave, y la varriable CLAVE llevará el valor de la clave.

Modificación/Sobreescritura
Dependerá del tipo de acceso.
SEQUENTIAL(secuencial)
REWRITE FILE (FICHERO3) FROM (REG_ENTSAL);


Donde REWRITE indica sobreescritura.

DIRECT(directo)
REWRITE FILE (FICHERO3) FROM (REG_ENTSAL) KEY(CLAVE);


Borrado
Dependerá del tipo de acceso.
SEQUENTIAL(secuencial)
Todo borrado debe estar precedido por una operación de lectura.
DELETE FILE (FICHERO4);


Donde DELETE indica que debe borrarse el último registro leído del fichero especificado.

DIRECT(directo)
DELETE FILE (FICHERO4) KEY (CLAVE);


Donde DELETE indica que debe borrarse el registro del fichero y clave especificados.

FICHEROS STREAM
El acceso siempre es de tipo SEQUENTIAL(secuencial). Los datos se guardan como una cadena de caracteres.
Existen 3 tipos de declaración para estos ficheros:
1. Explícita.
DCL SYSIN FILE STREAM INPUT;
DCL SYSPRINT FILE PRINT;


Donde STREAM indica que el fichero se procesará por flujo de caracteres.
PRINT indica que el fichero es de tipo impresora.

Estos dos ejemplos se refieren a la SYSIN de un JOB y a la cola del SYSPRINT. En la SYSIN escribiremos los datos que va a leer el programa, y en ek SYSPRINT escribiremos datos de salida.
El resto de posibles atributos son los mismos que para los ficheros RECORD.

2. Implícita.
Si omitimos el nombre del fichero en una operación de lectura (READ), el sistema supondrá que se trata de un fichero STREAM INPUT (SYSIN). En caso de la escritura (WRITE) se asumirá STREAM OUTPUT (SYSPRINT).

3. Por defecto.
Si se realiza una operación de entrada/salida contra un fichero que no tenga atributos se considerará STREAM por defecto.

Apertura/Cierre
Es el mismo caso que para los ficheros RECORD.

Lectura
GET FILE (SYSIN) LIST/DATA/EDIT;


Donde GET indica orden de lectura.
SYSIN indica que se trata del fichero del sistema. Si no se indica nombre asume SYSIN.
LIST/DATA/EDIT: son las opciones de lectura que se pueden indicar.
LIST: requiere que los datos de entrada estén en formato dato1,...,datoN. Entre cada dato debe haber una coma o un espacio en blanco. Cada dato se leerá sobre la variable indicada.
SYSIN DD *
datos1 datos2 datos3
/*

GET FILE LIST(variable_1, variable_2, variable_3);


DATA: requiere que la entrada esté en el formato var_1 = dato_1. Entre und ato y la variable siguiente debe haber una coma o un espacio en blanco. Cada dato se copiará sobre su variable correspondiente.
SYSIN DD *
var1=datos1 var2=datos2 var3=datos3
/*

GET FILE DATA (variable_1,variable_2,variable_3);


EDIT: los datos están dispuestos igual que en la opción LIST, pero podemos añadir el formato en que los leemos.
SYSIN DD *
campo1 campo2 campo3
/*

GET FILE EDIT (variable1,variable2,variable3) (A,A,A);


Los formatos que se pueden indicar son:
SKIP(n): salta n líneas antes de leer.
COL(n): se posiciona en la columna n antes de leer.
A: datos alfanuméricos.
P'formato PIC': datos numéricos. Ej P'999'.
X(n): salta n posiciones antes de leer.

Vamos a fijarnos en la opción SKIP.
Pensad que estamos leyendo de la SYSIN, y con SKIP le estamos indicando que salte a la siguiente línea de la SYSIN:
SYSIN DD *
campo1
campo2
campo3
/*


Esto en COBOL no sería posible, pues la SYSIN tiene una única línea que se recupera mediante un ACCEPT.
Sin embargo en PL/I podemos introducir mucha más información a través de la SYSIN.

Escritura
PUT FILE (SYSPRINT) LIST/DATA/EDIT;


Donde PUT indica orden de escritura.
Si no se indica nombre del fichero el sistema asume SYSPRINT.
LIST/DATA/EDIT son las mismas opciones que para el GET, pero referido a los datos que vamos a escribir. En EDIT se ñade la opción de "PAGE" para hacer salto de página al escribir.

"Los PUT" en PL/I son el equivalente a los DISPLAYs en COBOL. Cuando necesitemos "displayar" alguna variable para controles de errores, buscar un fallo en el programa, etc. lo haremos con un PUT y la información saldrá en el SYSPRINT en lugar de salir en la SYSOUT.
Tanto para programa ONLINE como BATCH, podremos ver los "PUTs" en el SYSPRINT.


En próximos artículos veremos ejemplos concretos del uso de estos ficheros. Hasta entonces, cualquier duda preguntad sin problema : )

miércoles, 1 de junio de 2011

Cruce de 3 ficheros: cruce 1-1-1

Continuando con la saga de los programas de cruce, puede darse el caso de que queramos comparar tres ficheros en lugar de dos.

El ejemplo que vamos a ver es el más simple, donde la clave no se repite en ninguno de los ficheros, es decir, se trata de un cruce 1-1-1.

En nuestro ejemplo, el archivo principal será el número 2.
Podéis escoger cualquiera de ellos, porque lo que buscamos es que la clave existe en los tres.

La base del proceso de cruce sería:

EVALUATE TRUE

   WHEN Clave1 = Clave2 = Clave3
   WHEN Clave1 < Clave2
   WHEN Clave3 < Clave2
   WHEN Clave1 > Clave2
   WHEN Clave3 > Clave2

END-EVALUATE


En cada uno de los "WHEN" escribiremos el código de lo que queramos hacer. Por ejemplo, cuando las 3 claves coincidan, escribiremos un registro en el fichero de salida.



Al inicio del programa leeremos el primer registro de cada fichero e informaremos los campos Clave1, Clave2 y Clave3:
Clave1 = 1; Clave2 = 1; Clave3 = 1.

Repetiremos el proceso hasta el final de alguno de los tres ficheros (pues si llegamos al final de alguno de los ficheros, las 3 claves ya no coincidirán):

PERFORM 3000-PROCESO
  UNTIL FIN-FICHERO1
     OR FIN-FICHERO2
     OR FIN-FICHERO3


a) Clave1 = Clave2 = Clave3:
Realizamos las funciones necesarias. En nuestro ejemplo, escribir en un fichero de salida.
Leemos el siguiente registro de cada uno de los ficheros.
Clave1 = 2; Clave2 = 3; Clave3 = 2.

b) Clave1 < Clave2:
Esto significa que la Clave1 no existe en los 3 ficheros. Leemos el siguiente registro del fichero 1 (el de menor clave).
Clave1 = 3; Clave2 = 3; Clave3 = 2.

c) Clave3 < Clave2:
Esto significa que la Clave3 no existe en los 3 ficheros. Leemos el siguiente registro del fichero 3 (el de menor clave).
Clave1 = 3; Clave2 = 3; Clave3 = 5.

d) Clave3 > Clave2:
Esto significa que la Clave2 no existe en los 3 ficheros. Leemos el siguiente registro del fichero 2 (el de menor clave).
Clave1 = 3; Clave2 = 5; Clave3 = 5.

e) Clave1 < Clave2:
Esto significa que la Clave1 no existe en los 3 ficheros. Leemos el siguiente registro del fichero 1 (el de menor clave).
Clave1 = 6; Clave2 = 5; Clave3 = 5.

f) Clave1 > Clave2:
Esto significa que la Clave2 no existe en los 3 ficheros. Leemos el siguiente registro del fichero 2 (el de menor clave).
Clave1 = 6; Clave2 = 6; clave3 = 5.

g) Clave3 < Clave2:
Esto significa que la Clave3 no existe en los 3 ficheros. Leemos el siguiente registro del fichero 3 (el de menor clave).
Clave1 = 6; Clave2 = 6; Clave3 = 6.


El proceso descrito se refiere al ejemplo del esquema "Curece 1-1-1 sencillo". Pero si os fijáis, lo importante al comparar las claves es leer siempre del fichero con menor clave, para ir posicionándonos correctamente sin dejar ninguna comparación por hacer.

Este ejemplo es MUY sencillo. Muchas veces cuando cruzamos 3 ficheros no buscamos los registros cuyas 3 claves coincidan, sino que puede ser que necesitemos los registros con Clave1 = Clave2 por un lado, y los registros con Clave3 = Clave2 por otro lado.

Aquí la cosa se complica (y ya no digamos si las claves se repiten n veces), porque ya no podemos hacer las lecturas en cada comparación, sino que tendremos que hacer las comparaciones necesarias en primer lugar, y las lecturas correspondientes al final del proceso.

Pero todo esto lo veremos en otro artículo : )

lunes, 9 de mayo de 2011

Fechas: Juliana, Gregoriana, DB2...

Para aquellos que habéis estado en más de un proyecto, seguro que os ha pasado que en cada sitio almacenan las fechas de una manera distinta. En este artículo vamos a ver las formas más comunes de almacenar fechas y como pasarlas de uno a otro formato.

Fecha DB2
Es aquella que tiene formato "DATE" en la correspondiente tabla.
Su definición sería: AAAA-MM-DD

Ejemplos
COBOL: 01  WX-FECHA-DB2 PIC X(10) VALUE '2011-01-01'.
PL/I : DCL WX_FECHA_DB2 CHAR(10)  INIT ('2011-01-01');


Fecha Juliana
En tabla se tendría formato DECIMAL (7), pues se trata de un número de 7 dígitos. La información que almacena es el año de 4 dígitos, y el día del año entre 1 y 365. Su definición sería: AAAADDD

Ejemplos
COBOL: 01  WX-FECHA-JUL PIC 9(7)   VALUE 2011001.
PL/I : DCL WX_FECHA_JUL PIC '(7)9' INIT (2011001);


Fecha gregoriana
En tabla tendría formato DECIMAL (8), pues se trata de un número de 8 dígitos. La información que se almacena es el año de 4 dígitos, el mes y el día del mes.
Su definición sería: AAAAMMDD

Ejemplos
COBOL: 01  WX-FECHA-GREG PIC 9(8)   VALUE 20110101.
PL/I : DCL WX_FECHA_GREG PIC '(8)9' INIT (20110101);


Pero a la hora de mostrar una fecha por pantalla, no se usa ninguno de esos formatos, sino el más entendible por el usuario: el mítico DD-MM-AAAA (01-01-2011).
Ya sea con guiones o con barras, la transformación a este formato suele ser común en los programas online.
Vamos a ver como transformaríamos cada uno de los formatos anteriores al formato de pantalla.

De AAAA-MM-DD a DD-MM-AAAA
(...)
WORKING-STORAGE SECTION.
01 WX-FECHA-DB2.
   05 WX-AAAA-DB2    PIC 9(4).
   05 FILLER         PIC X.
   05 WX-MM-DB2      PIC 9(2).
   05 FILLER         PIC X.
   05 WX-DD-DB2      PIC 9(2).

01 WX-FECHA-ONLINE.
   05 WX-DD-ONLINE   PIC 9(2).
   05 FILLER         PIC X VALUE '-'.
   05 WX-MM-ONLINE   PIC 9(2).
   05 FILLER         PIC X VALUE '-'.
   05 WX-AAAA-ONLINE PIC 9(4).

PROCEDURE DIVISION.
 

     MOVE '1999-31-12' TO WX-FECHA-DB2

     MOVE WX-AAAA-DB2  TO WX-AAAA-ONLINE
     MOVE WX-MM-DB2    TO WX-MM-ONLINE
     MOVE WX-DD-DB2    TO WX-DD-ONLINE

     DISPLAY 'FECHA ONLINE:' WX-FECHA-ONLINE

     STOP RUN
     .



Como veis este cambio no tiene mucha ciencia^^

De AAAAMMDD a DD-MM-AAAA
(...)
WORKING-STORAGE SECTION.
01 WX-FECHA-GREG.
   05 WX-AAAA-8      PIC 9(4).
   05 WX-MM-8        PIC 9(2).
   05 WX-DD-8        PIC 9(2).

01 WX-FECHA-ONLINE.
   05 WX-DD-ONLINE   PIC 9(2).
   05 FILLER         PIC X VALUE '-'.
   05 WX-MM-ONLINE   PIC 9(2).
   05 FILLER         PIC X VALUE '-'.
   05 WX-AAAA-ONLINE PIC 9(4).

PROCEDURE DIVISION.

     MOVE 19991231  TO WX-FECHA-8
 
     MOVE WX-AAAA-8 TO WX-AAAA-ONLINE
     MOVE WX-MM-8   TO WX-MM-ONLINE
     MOVE WX-DD-8   TO WX-DD-ONLINE

     DISPLAY 'FECHA ONLINE:' WX-FECHA-ONLINE

     STOP RUN
     .


Veis que es prácticamente lo mismo que en el caso anterior.

También puede ser necesario transformar las fechas entre el formato juliano y el gregoriano:

De AAAAMMDD a AAAADDD
(...)
WORKING-STORAGE SECTION.
01 WX-FECHA-JUL.
   05 WX-AAAA-JUL PIC 9(4).
   05 WX-DDD-JUL  PIC 9(3).

01 WX-FECHA-GREG.
   05 WX-AAAA-8   PIC 9(4).
   05 WX-MM-8     PIC 9(2).
   05 WX-DD-8     PIC 9(2).

PROCEDURE DIVISION.

     MOVE 20111231  TO WX-FECHA-GREG

     MOVE WX-AAAA-8 TO WX-AAAAA-JUL
     MOVE WX-DD-8   TO WX-DD-JUL

     IF WX-MM-8 EQUAL 12
        ADD 30      TO WX-DD-JUL
        SUBSTRACT 1 FROM WX-MM-8
     END-IF

     IF WX-MM-8 EQUAL 11
        ADD 31      TO WX-DD-JUL
        SUBSTRACT 1 FROM WX-MM-8
     END-IF

     IF WX-MM-8 EQUAL 10
        ADD 30      TO WX-DD-JUL
        SUBSTRACT 1 FROM WX-MM-8
     END-IF

     IF WX-MM-8 EQUAL 9
        ADD 31      TO WX-DD-JUL
        SUBSTRACT 1 FROM WX-MM-8
     END-IF

     IF WX-MM-8 EQUAL 8
        ADD 31      TO WX-DD-JUL
        SUBSTRACT 1 FROM WX-MM-8
     END-IF

     IF WX-MM-8 EQUAL 7
        ADD 30      TO WX-DD-JUL
        SUBSTRACT 1 FROM WX-MM-8
     END-IF

     IF WX-MM-8 EQUAL 6
        ADD 31      TO WX-DD-JUL
        SUBSTRACT 1 FROM WX-MM-8
     END-IF

     IF WX-MM-8 EQUAL 5
        ADD 30      TO WX-DD-JUL
        SUBSTRACT 1 FROM WX-MM-8
     END-IF

     IF WX-MM-8 EQUAL 4
        ADD 31      TO WX-DD-JUL
        SUBSTRACT 1 FROM WX-MM-8
     END-IF

     IF WX-MM-8 EQUAL 3
        ADD 28      TO WX-DD-JUL
        SUBSTRACT 1 FROM WX-MM-8
     END-IF

     DIVIDE WX-AAAA-8 BY 4 GIVING WX-AAAA-8 REMAINDER WX-RESTO

     IF WX-RESTO EQUAL ZEROES
        ADD 1       TO WX-DDD-JUL
     END-IF

     IF WX-MM-8 EQUAL 2
        ADD 31      TO WX-DD-JUL
     END-IF

     DISPLAY 'FECHA JULIANA (YYYYDDD):' WX-FECHA-JUL

     STOP RUN
     .



Aquí ya se complica un poco la cosa. Normalmente suelen existir rutinas generales para hacer este formateo. Por ejemplo, una rutina que recibe la fecha en formato AAAAMMDD por linkage y la devuelve en formato AAAADDD.

De AAAADDD a AAAAMMDD
(...)
WORKING-STORAGE SECTION.
01 TABLA-MM-DD.
   05 MM-01       PIC 9(3) COMP-3 VALUE 0.
   05 MM-02       PIC 9(3) COMP-3 VALUE 31.
   05 MM-03       PIC 9(3) COMP-3 VALUE 59.
   05 MM-04       PIC 9(3) COMP-3 VALUE 90.
   05 MM-05       PIC 9(3) COMP-3 VALUE 120.
   05 MM-06       PIC 9(3) COMP-3 VALUE 151.
   05 MM-07       PIC 9(3) COMP-3 VALUE 181.
   05 MM-08       PIC 9(3) COMP-3 VALUE 212.
   05 MM-09       PIC 9(3) COMP-3 VALUE 243.
   05 MM-10       PIC 9(3) COMP-3 VALUE 273.
   05 MM-11       PIC 9(3) COMP-3 VALUE 304.
   05 MM-12       PIC 9(3) COMP-3 VALUE 334.

01 TABLA-MM-DD-R REDEFINES TABLA-MM-DD.
   05 MM-DIAS OCCURS 12 TIMES
                  PIC 9(3) COMP-3.
01 WX-ANO-BIS     PIC S9(3) COMP-3.
   88 ANO-BIS-SI                  VALUE 0.
01 WX-FIL9        PIC S9(3) COMP-3.
01 WX-FECHA-JUL.
   05 WX-AAAA-JUL PIC 9(4).
   05 WX-DDD-JUL  PIC 9(3). 

01 WX-FECHA-GREG.
   05 WX-AAAA-8   PIC 9(4).
   05 WX-MM-8     PIC 9(2).
   05 WX-DD-8     PIC 9(2).

PROCEDURE DIVISION.
     DISPLAY 'FECHA JULIANA: 2011059'

     MOVE 2011059     TO WX-FECHA-JUL
     MOVE WX-AAAA-JUL TO WX-AAAA-8 


*--- COMPROBAR AÑO BISIESTO: RESTO = 0 = AÑO BISIESTO
     DIVIDE WX-AAAA-JUL BY 4 GIVING WX-FIL9 REMAINDER WX-ANO-BIS

*--- CORREGIR VALORES MM-DD PARA AÑO BISIESTO
     IF ANO-BIS-SI
        ADD 1 TO MM-03
                 MM-04
                 MM-05
                 MM-06
                 MM-07
                 MM-08
                 MM-09
                 MM-10
                 MM-11
                 MM-12
     END-IF

*--- DETERMINA EN QUE MES ESTAMOS
     PERFORM VARYING WX-MM-8 FROM 12 BY -1
        UNTIL MM-DIAS(WX-MM-8) < WX-DDD-JUL 

     END-PERFORM 

*--- EL RESTO DE DDD SON LOS DIAS DENTRO DEL MES 
     SUBTRACT MM-DIAS(WX-MM-8) FROM WX-DDD-JUL GIVING WX-DD-8 

     DISPLAY 'FECHA AAAAMMDD:' WX-FECHA-GREG

     STOP RUN 
     .



Por supuesto puede haber otras maneras de hacer estos cambios. Existen también funciones intrínsecas de COBOL que hacen la transformación, pero normalmente no dejan usarlas porque consumen muchos recursos.