|
Si borramos por error una dataset de z/OS (fichero o librería), podemos recuperar su copia de seguridad mediante el comando HRECOVER de TSO. Como cualquier comando TSO, podemos introducirlo bien en la línea de comandos de cualquier panel ISPF, bien en el panel Command (opción 6 del panel principal de ISPF).
Sintaxis:
HRECOVER dataset borrado NEWNAME(nuevo nombre)
Ejemplo:
En el panel Command:
HRECOVER USUARIO.DES.FUENTES NEWNAME(USUARIO.DES.RECUP)
En la línea de comandos:
TSO HRECOVER USUARIO.DES.FUENTES NEWNAME(USUARIO.DES.RECUP)
Nota: cuando el texto de un comando excede la longitud de la línea de comandos (como en este caso), podemos abrir una ventana con más lineas mediante el comando ZEXPAND de ISPF.
En este artículo vamos a analizar las diferentes formas que ofrece COBOL de hacer búsquedas en tablas
Recordemos que una tabla es cualquier variable definida con más de una ocurrencia o que pertenece a un grupo de nivel superior con más de una ocurrencia.
Por ejemplo, supongamos una tabla que almacene nombres de personas definida así:
01 TABLA.
05 FILAS OCCURS 20.
10 NOMBRE PIC X(15).
10 APELLIDO PIC X(30).
Para buscar a una persona por apellido, podríamos emplear un bucle construido con un PERFORM VARYING. Tendríamos que definir en Working Storage una variable numérica para utilizarla como índice y un switch o indicador que permita saber si el apellido se ha encontrado no.
01 WS-IND PIC 9(2) COMP.
01 WS-APELLIDO PIC X(30) VALUE “GARCIA”.
01 SW-INDICADOR PIC X VALUE SPACES.
88 NO-ENCONTRADO VALUE “N”.
88 ENCONTRADO VALUE “S”.
SET NO-ENCONTRADO TO TRUE
PERFORM VARYING WS-IND FROM 1 BY 1 UNTIL WS-IND > 20 OR ENCONTRADO
IF APELLIDO(IND) = WS-APELLIDO
SET ENCONTRADO TO TRUE
END-IF
END-PERFORM
IF NO-ENCONTRADO
DISPLAY “Apellido no econtrado”
END-IF
El bloque de código anterior se podría simplificar con el uso de la instrucción SEARCH, que nos evita tener que declarar un índice explícitamente y resulta más legible. Tendríamos que cambiar la definición de la tabla para incluir el índice en la misma, de forma que quedara así:
01 TABLA.
05 FILAS OCCURS 20 INDEXED BY IND.
10 NOMBRE PIC X(15).
10 APELLIDO PIC X(30).
Este tipo de índices asociados a la tabla tienen una representación interna especial, por lo que solo es posible modificar su valor mediante la instrucción SET. No se pueden utilizar en operaciones aritméticas ni en un DISPLAY. En una búsqueda con SEARCH solo hay que especificar el valor del índice en el que queremos que se empiece a buscar.
La búsqueda anterior quedaría de este modo con SEARCH:
SET NO-ENCONTRADO TO TRUE
SET IND TO 1
SEARCH FILAS
AT END
DISPLAY “Apellido no encontrado”
WHEN APELLIDO(IND) = WS-APELLIDO
SET ENCONTRADO TO TRUE
END-SEARCH
La condición lógica del WHEN se irá evaluando para todos los elementos de la tabla hasta que sea verdadera o se llegue al final. Si se ha llegado al último elemento sin cumplirse la condición, se ejecutará la instrucción indicada en el AT END.
Como hemos comentado, las búsquedas anteriores implican recorrer todos los elementos de la tabla hasta encontrar el buscado. Existe una forma más eficiente de buscar en tablas llamada búsqueda binaria (instrucción SEARCH ALL), que utiliza un algoritmo especial. Su inconveniente es que requiere que la tabla esté ordenada por un campo clave. Además, los elementos vacíos deben contener HIGH-VALUES si el orden es ascendente o LOW-VALUES si es descendente. Si no se cumplen estos requisitos, la búsqeda devolverá resultados incorrectos.
Para evitar este último requisito, podemos utilizar la cláusula DEPENDING ON, que permite definir un campo numérico para almacenar número de elementos informados de la tabla. Si actualizamos el valor de este campo al añadir elementos, no será necesario llenar el resto de la tabla con HIGH-VALUES o LOW-VALUES.
Este ejemplo de definición de tabla nos permitiría hacer búsqueda binaria sin necesidad de inicializar la tabla con HIGH-VALUES o LOW-VALUES.
01 MAX-TABLA PIC 9(2).
01 TABLA.
05 FILAS OCCURS 1 TO 20 DEPENDING ON MAX-TABLA
ASCENDING KEY IS APELLIDO
INDEXED BY IND.
10 NOMBRE PIC X(15).
10 APELLIDO PIC X(30).
Hemos indicado orden ascendente. Para orden descendente se utilizaría DESCENDING KEY IS.
En el código de la búsqueda habría que cambiar SEARCH por SEARCH ALL y se puede quitar la inicialización del índice porque se hace automáticamente. El campo MAX-TABLA debe contener la posición del último elemento informado.
SET NO-ENCONTRADO TO TRUE
SEARCH ALL FILAS
AT END
DISPLAY “Apellido no encontrado”
WHEN APELLIDO(IND) = WS-APELLIDO
SET ENCONTRADO TO TRUE
END-SEARCH
En la definición de los registros de salida de un sort con BUILD se puede incluir una barra (carácter “/”) para insertar un registro en blanco. Los campos y/o literales que se incluyan a continuación formarán parte del nuevo registro.
Por defecto se inserta un único registro. Si añadimos un número delante de la barra, se insertarán n registros en blanco.
Este separador solo puede incluirse dentro de un BUILD o OUTREC que forme parte de un OUTFIL.
Ejemplo:
//PASSORT EXEC PGM=SORT
//SYSOUT DD SYSOUT=*
//SORTIN DD DSN=FICH.ENTRADA,DISP=SHR
//
//SALIDA1 DD DSN=FICH.SALIDA1,
// DISP=(,CATLG,DELETE),
// SPACE=(TRK,(10,10),RLSE),
// RECFM=FB
//SALIDA2 DD DSN=FICH.SALIDA2,
// DISP=(,CATLG,DELETE),
// SPACE=(TRK,(10,10),RLSE),
// RECFM=FB
//SYSIN DD *
SORT FIELDS=COPY
OUTFIL FNAMES=SALIDA1,BUILD=(1,10,/,11,10)
OUTFIL FNAMES=SALIDA2,BUILD=(1,10,11,10,2/)
En SALIDA1, por cada registro del fichero de entrada generamos dos registros en el fichero de salida con campos de entrada diferentes.
En SALIDA 2, por cada registro del fichero de entrada generamos un registro con campos del fichero de entrada y dos registros en blanco.
Las cláusulas OPTIMIZE FOR n ROWS y FETCH FIRST n ROWS ONLY de DB2 permiten optimizar una SELECT indicándole al DB2 el número de filas (n) que estimamos se van a obtener. La diferencia entre ambas es que con OPTIMIZE FOR se recuperan todas las filas que cumplan la condición de la SELECT y con FETCH FIRST sólo las n primeras.
IBM recomienda utilizar OPTIMIZE for 1 ROW siempre que se vaya recuperar un número reducido de filas (aunque sea superior a uno). De esta forma le indicamos a DB2 que intente evitar caminos de acceso que impliquen ordenaciones.
Ejemplo:
SELECT CAMPO1,
CAMPO2,
CAMPO3
FROM TABLA
WHERE CAMPO4 = :VARIABLE
FETCH FIRST 10 ROWS ONLY
Esta SELECT recuperará un máximo de 10 filas. Un intento de recuperar la número 11 devolverá un SQLCODE 100.
El FETCH FIRST implica también optimizar el camino de acceso para el mismo número de filas. Es decir, en el ejemplo anterior se asume por omisión un OPTIMIZE FOR 10 ROWS.
Cuando queramos hacer una optimización para un número inferior de filas, debemos indicarlo explícitamente. Por ejemplo, para optimizar la SELECT anterior siguiendo la recomendación de IBM:
SELECT CAMPO1,
CAMPO2,
CAMPO3
FROM TABLA
WHERE CAMPO4 = :VARIABLE
FETCH FIRST 10 ROWS ONLY
OPTIMIZE FOR 1 ROW
Cuando solo queramos optimizar la SELECT sin limitar el número de filas, basta con incluir el OPTIMIZE:
SELECT CAMPO1,
FROM TABLA
WHERE CAMPO2 = :VARIABLE
OPTIMIZE FOR 1 ROW
<
En este artículo vamos a introducir las sentencias de control del parámetro OUTFIL del Sort que permiten generar un fichero de salida con formato de informe. Las principales son:
HEADER1
Cabecera general del informe.
HEADER2
Cabecera de página.
HEADER3
Cabecera de sección.
TRAILER1
Pie final del informe
TRAILER2
Pie de página.
TRAILER3
Pie de sección.
SECTIONS
Campo/s de ruptura que marcan el final de una sección y el inicio de la siguiente.
Su sintaxis completa se puede encontrar en el manual de IBM. Aquí vamos a aprender a utilizarlas mediante un sencillo ejemplo.
Partimos de un fichero con las ventas que han realizado los comerciales de una empresa. Tiene la siguiente estructura:
Campo |
Formato COBOL |
COMERCIAL |
PIC X(25) |
SUCURSAL |
PIC X(10) |
TOTAL VENDIDO |
PIC 9(9)V9(2) COMP-3 |
COMISIONES |
PIC 9(9)V9(2) COMP-3 |
Y queremos obtener un informe que muestre las ventas y comisiones de cada vendedor, así como los totales de cada sucursal. Tendrá cabeceras de página y de sección (sucursal) y pies de sección.
23/04/2014 PAG. 1
INFORME DE VENTAS Y COMISIONES POR SUCURSAL
-------------------------------------------
SUCURSAL: BARCELONA
NOMBRE SUCURSAL VENTA COMISION
------------------------ ---------- ------------ ------------
NOMBRE APELL1 APELL2 BARCELONA 4108,92 350,65
NOMBRE APELL1 APELL2 BARCELONA 12789,23 1190,67
NOMBRE APELL1 APELL2 BARCELONA 2109,34 208,65
-----------------------------------------------------------------------
TOTAL BARCELONA 19007,49 1749,97
SUCURSAL: LEVANTE
NOMBRE SUCURSAL VENTA COMISION
------------------------ ---------- ------------ ------------
NOMBRE APELL1 APELL2 LEVANTE 8177,01 711,23
… … … …
TOTAL LEVANTE 39022,42 3749,92
…
23/04/2014 PAG. 2
INFORME DE VENTAS Y COMISIONES POR SUCURSAL
-------------------------------------------
…
Para generarlo utilizaríamos el siguiente paso de Sort:
//PA0010 EXEC PGM=SORT
//SYSOUT DD SYSOUT=*
//SORTIN DD DSN=FICHERO.ENTRADA,
// DISP=SHR
//SALIDA DD DSN=FICHERO.SALIDA,
// DISP=(NEW,CATLG,DELETE),RECFM=FB,DSORG=PS,LRECL=72
//SYSIN DD *
SORT FIELDS=(26,10,CH,A)
OUTFIL FNAMES=SALIDA,
BUILD=(1X,1,25,2X,26,10,2X,36,6,PD,EDIT=(IIIIIIITT,TT),
2X,42,6,PD,EDIT=(IIIIIIITT,TT),6X),
REMOVECC,
HEADER2=(50X,DATE=(DM4/),2X,'PAG.',PAGE,/,
10X,'INFORME DE VENTAS Y COMISIONES POR SUCURSAL',/,
10X,'-------------------------------------------'),
SECTIONS=(26,10,
HEADER3=(/,2:'SUCURSAL: ',26,10,/,
/,2:'NOMBRE',29:'SUCURSAL',41:'VENTA',
55:'COMISION',/,
2:'------------------------',
29:'----------',
41:'------------',
55:'------------'),
TRAILER3=(2:71'-',/,1X,'TOTAL ',26,10,23X,
TOTAL=(36,6,PD,EDIT=(IIIIIIITT,TT)),
2X,
TOTAL=(42,6,PD,EDIT=(IIIIIIITT,TT))))
Vemos que se ordena por el campo de ruptura (sucursal, posición 26). A continuación se incluye la ficha OUTFIL correspondiente al fichero de salida. Dentro de ella incluimos el parámetro BUILD para generar los registros de detalle y a continuación, los parámetros que definen el informe. Algunos de ellos ya los hemos descrito en la introducción; los nuevos que aparecen en el ejemplo son:
REMOVECC
Elimina los caracteres de control que se incluyen por defecto
TOTAL
Parámetro que se puede incluir dentro de cualquiera de los pies del informe (TRAILER1, TRAILER2 y TRAILER3). Muestra la suma de todos los valores del campo indicado. En la suma se incluirán todos los registros del informe, pagina o sección, dependiendo del pie en el que se sitúe.
En un Sort es posible forzar un código de retorno determinado (0, 4 u 8) si el SORTOUT (fichero de salida) no tiene registros. Se hace mediante la opción NULLOUT = RCn, donde n puede tomar el valor 0, 4 u 8 (el valor por defecto es 0).
Gracias a esta utilidad podemos construir un paso de JCL que devuelva retorno 4 si el fichero de entrada está vacío y 0 en caso contrario. Como solo necesitamos leer un registro del fichero de entrada, usamos también la instrucción STOPAFT = 1.
Ejemplo:
//PA0010 EXEC PGM=SORT
//SYSOUT DD SYSOUT=*
//SORTIN DD DSN=FICHERO.PRUEBA.NULLOUT,DISP=SHR
//SORTOUT DD DSN=&&TEMP,DISP=(,PASS)
//SYSIN DD *
OPTION COPY,NULLOUT=RC4,STOPAFT=1
La existencia de un fichero se puede controlar mediante el comando LISTCAT de la utilidad IDCAMS. Esta devolverá código de retorno 4 si el fichero que se le pasa como parámetro no existe en el catálogo.
Ejemplo:
Tenemos un JOB que recibe un fichero de entrada. En caso de que dicho fichero no exista, queremos que el JOB no haga nada pero termine sin JCL Error.
//PA0010 EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN DD *
LISTCAT ENTRIES (FIC.ENTRAD.EJEMPLO) ALL
/*
//IFPAS1 IF PA0010.RC EQ 0 THEN
//PA0020 …
//PA0030 …
//…
//ENPAS1 ENDIF
Recordemos que IDCAMS nos permite cambiar el código de retorno de la ejecución. Por ejemplo, si nos interesa que el job termine con retorno 1 en vez de 4, podemos forzarlo así:
LISTCAT ENTRIES (FIC.ENTRAD.EJEMPLO) ALL
IF LASTCC NE 0 THEN DO
SET MAXCC=1
END
El COBOL de IBM para z/OS incluye dos funciones que simplifican mucho los cálculos con fechas, gracias a que convierten una fecha en formato AAAAMMDD en su equivalente numérico y a la inversa. Ello nos permite hacer sumas o restas con fechas fácilmente, sin necesidad de tener en cuenta el número de días de cada mes o si alguno de los años que intervienen en el cálculo es bisiesto.
Las funciones son:
INTEGER-OF-DATE (argumento)
El argumento debe ser un número entero que represente una fecha en formato AAAAMMDD comprendida entre 16010101 y 99991231.
El resultado es un número entero en el rango de 1 a 3.067.671 que representa el número de días transcurridos entre el 31 de diciembre de 1600 y la fecha del argumento.
DATE-OF-INTEGER (argumento)
El argumento debe ser un número entero comprendido entre 1 y 3.067.671 que represente el número de días transcurridos desde el 31 de diciembre de 1600.
El resultado es la fecha en formato AAAAMMDD equivalente al número entero pasado como argumento.
Ejemplos:
Definimos las variables
01 W-FECHA1 PIC 9(08).
01 W-FECHA2 PIC 9(08).
01 W-DIAS PIC 9(04).
1) Calcular el número días de diferencia entre el 5 de marzo de 2012 y el 10 de febrero de 2011.
MOVE 20120305 TO W-FECHA2
MOVE 20110210 TO W-FECHA1
COMPUTE W-DIAS =
FUNCTION INTEGER-OF-DATE(W-FECHA2) -
FUNCTION INTEGER-OF-DATE(W-FECHA1)
2) Restarle diez días al 7 de marzo de 2013.
MOVE 20130307 TO W-FECHA1
COMPUTE W-FECHA2 =
FUNCTION DATE-OF-INTEGER (
FUNCTION INTEGER-OF-DATE(W-FECHA1) - 10 )
La conversión de un fichero de longitud variable a fija y viceversa en un Sort se hace mediante las opciones VTOF y FTOV del parámetro OUTFIL del Sort.
Recordemos que el parámetro OUTFIL permite generar de 1 a n ficheros de salida. Cada uno de estos puede ser construido de forma independiente mediante instrucciones BUILD o OUTREC y condiciones INCLUDE u OMIT.
Conversión de variable a fija
La opción VTOF (o su sinónimo CONVERT) incluida en un grupo OUTFIL indica que los registros de entrada de longitud variable deben ser convertidos a longitud fija.
Se debe incluir también un parámetro BUILD o OUTREC, que generará un registro reformateado de longitud fija sin el descriptor de registro de 4 bytes (los datos comenzarán en la posición 1).
Los campos que se incluyan en el parámetro BUILD o OUTREC deberán estar referidos a los registros de entrada de longitud variable (los datos empiezan en la posición 5).
VTOF utiliza por defecto la opción VLFILL=X’40’ (relleno con espacios) para que sea posible procesar registros de entrada de longitud variable que sean demasiado cortos para contener todos los campos especificados en BUILD o OUTREC.
El formato de registro del fichero de salida será FB por defecto. Y si se especifica, deberá ser FB u otro de los de longitud fija
Si se utiliza VTOF con FTOV, IFTRAIL, IFTHEN, FINDREP u OVERLAY, el Sort terminará con error.
Ejemplo:
//SORT006 EXEC PGM=SORT
//SYSOUT DD SYSOUT=*
//SORTIN DD DSN=ENTRADA...,
// DISP=SHR
//SALIDA1 DD DSN=SALIDA...,
// DCB=(RECFM=FB,LRECL=300,BLKSIZE=0,DSORG=PS),
// DISP=(,CATLG,DELETE),
// SPACE=(TRK,(2,1),RLSE)
//SYSIN DD *
SORT FIELDS=COPY
OUTFIL FNAMES=SALIDA1,VTOF,BUILD=(5,300)
/*
* En vez de VTOF podemos usar su sinónimo CONVERT:
OUTFIL FNAMES=SALIDA1,CONVERT,BUILD=(5,300)
Conversión de fija a variable
FTOV incluida en un grupo OUTFIL indica que los registros de entrada de longitud fija deben ser convertidos a longitud variable.
Si no se incluye un parametro OUTREC, BUILD, OVERLAY, FINDREP o IFTHEN, los registros de entrada de longitud fija completos se transforma en registros de salida de longitud variable
Si se incluye un parametro OUTREC, BUILD, OVERLAY, FINDREP o IFTHEN, los campos especificados se transforman en registros de salida de longitud variable.
En los dos casos, antes de escribir los registros de salida se les añade un descriptor de registro de 4 bytes (los datos empiezan en la posición 5).
Los campos especificados en OUTREC, BUILD, OVERLAY, FINDREP o IFTHEN deberán estar referidos a los registros de entrada de longitud fija (los datos empiezan en la posición 1).
El formato de registro del fichero de salida del OUTFIL será VB por defecto. Y si se especifica, deberá ser FB o cualquier otro de longitud variable.
Si no se especifica el parámetro LRECL para el fichero de salida, se le dará una longitud que permita contener el registro de mayor longitud.
Ejemplo:
EXEC PGM=SORT
//SYSOUT DD SYSOUT=*
//SORTIN DD DISP=SHR,DSN=ENTRADA…
//SALIDA DD DSN=SALIDA…
// DISP=(NEW,CATLG,DELETE),
// LRECL=448,RECFM=VB,DSORG=PS
//SYSIN DD *
OPTION COPY
OUTFIL FNAMES=SALIDA,FTOV,OUTREC=(1:1,444)
/*
Fuente: DFSORT Application Programming Guide v1R13
DISP es un parámetro de la instrucción DD (Data Definition) de los JCL. Sirve para indicarle al sistema el estado actual de un fichero y las acciones a realizar sobre el mismo si el paso termina correctamente y en caso de terminación anormal (abend). Su formato es:
DISP=(Estado, Acción normal, Acción anormal).
Valores para el subparámetro de estado
NEW
Crear nuevo dataset
OLD
El dataset ya existe y se require uso exclusivo (no compartido) del mismo.
SHR
El dataset ya existe y no se require su uso exclusivo. Otros jobs pueden utilizarlo al mismo tiempo.
MOD:
Si el dataset ya existe, indica que se quieren añadir registros al final del mismo (debe ser secuencial). Si el dataset no existe, indica que debe ser creado.
Valores para el subparámetro de acción normal
DELETE
Indica que el dataset dejará de ser necesario. Dependiendo de la configuración de seguridad y de si está almacenado en cinta o no, puede suponer que se eliminará físicamente o bien que su espacio quedará disponible para otros datasets.
KEEP
Mantener el dataset en el volumen en el que esté almacenado.
PASS
Pasar el dataset para su utilización en un paso posterior del job. Ahorra tiempo con respecto a KEEP, ya que el sistema retiene información sobre la ubicación y el volumen. De todas formas, es preferible evitar su su uso porque puede provocar pérdida de datos o incluso el borrado del fichero si no se codifica correctamente en ciertos casos.
CATLG
Incluir una entrada que apunte al dataset en el catálogo, lo que nos permitirá utilizar el dataset posteriormente.
UNCATLG
Si está activado el sistema de gestión automática de almacenamiento (SMS), que suele ser lo habitual, es ignorado y equivale a KEEP. Indica que se eliminará del catálogo la entrada correspondiente al dataset.
Valores para el subparámetro de acción anormal
DELETE
Liberar el espacio que ocupa el dataset, que quedará disponible para su uso por otros datasets.
KEEP
Mantener el dataset.
CATLG
Incluir una entrada que apunte al dataset en el catálogo, lo que nos permitirá utilizar el dataset posteriormente.
UNCATLG
Si está activado el sistema de gestión automática de almacenamiento (SMS), que suele ser lo habitual, es ignorado y equivale a KEEP. Indica que se eliminará del catálogo la entrada correspondiente al dataset.
Valores por defecto
Si se omite el suparámetro de estado, el valor por defecto es NEW.
Si se omite el subparámetro de ejecución normal, el valor por defecto es DELETE para un dataset nuevo y KEEP para un dataset existente.
Si se omite el suparámetro de ejecución anormal, el valor por defecto es el especificado para la ejecución normal, salvo que se trate de PASS. En ese caso el valor por defecto es DELETE para un dataset nuevo o KEEP para uno existente.
Para indicar la ausencia del primer y el segundo subparámetro se debe incluir la coma de separación.
Ejemplos:
DISP=(,CATLG,DELETE) – Equivale a DISP=(NEW,CATLG,DELETE)
DISP=(,KEEP) – Equivale a (NEW,KEEP,KEEP)
DISP=(OLD,,DELETE) – Equivale a (OLD,KEEP,DELETE)
Si se omite el parámetro DISP por completo, el valor por defecto es (NEW,DELETE,DELETE) Es decir, un fichero que se crea y se borra en el mismo paso.
Nota: Utilizamos el término inglés dataset para referirnos a los ficheros que se utilizan en el z/OS. Se trata de conjuntos de datos organizados en forma de registros y bloques que pueden ser almacenados en dispositivos de acceso directo (DASD) o en cinta.
|
|