Cuando empezamos a trabajar con ficheros, a veces se nos plantean muchas dudas sobre el hecho de tener que leer de mas de un fichero a la vez.
Son muchas las preguntas que me habéis planteado sobre este tema, así que aquí, en esta segunda parte dedicada a los ficheros voy a intentar que quede resuelta con un ejemplo práctico.
La situación
Vamos a utilizar un fichero de albaranes que a su vez integra una tabla.
Dos ficheros maestros, clientes y productos.
Un fichero de impresora.
¿Que vamos a hacer?
Vamos a leer secuencialmente el fichero de albaranes. Al mismo tiempo vamos a extraer el nombre del cliente y además vamos a actualizar las existencias de los productos.
También vamos a ir generando una línea de impresora por cada albarán con el importe total del mismo.
Lo primero que hacemos es iniciar nuestro programa con las dos divisiones primeras, donde indicamos el nombre del programa y los ficheros con los que vamos a trabajar.
Empezamos con nuestro programa:
-
- IDENTIFICATION DIVISION.
-
- PROGRAM-ID. FICHEROS.
-
- ENVIRONMENT DIVISION.
-
- CONFIGURATION SECTION.
-
- SPECIAL-NAMES. DECIMAL-POINT IS COMMA.
-
- INPUT-OUTPUT SECTION.
-
- FILE-CONTROL.
SELECT ALBARANES ASSIGN TO RANDOM «ALBARANES.DAT»
ORGANIZATION INDEXED ACCESS DYNAMIC
RECORD KEY ALB-NUMERO
FILE STATUS STA-ALBA.
SELECT CLIENTES ASSIGN TO RANDOM «CLIENTES.DAT»
ORGANIZATION INDEXED ACCESS RANDOM
RECORD KEY CLI-CODIGO
FILE STATUS STA-CLIEN.
SELECT PRODUCTOS ASSIGN TO RANDOM «PRODUCTOS.DAT»
ORGANIZATION INDEXED ACCESS RANDOM
RECORD KEY PRO-CODIGO
FILE STATUS STA-PRODU.
SELECT IMPRE ASSIGN TO PRINT «PRINTER1».
Como veis al archivo de albaranes le hemos indicado un acceso dinámico porque lo vamos a leer secuencialmente, mientras que a los ficheros maestros un acceso directo, ya que los vamos a acceder por su código.
A continuación describimos los campos de cada uno de los ficheros.
DATA DIVISION.
FILE SECTION.
FD ALBARANES LABEL RECORD STANDARD.
01 REGALBA.
-
- 02 ALB-NUMERO PIC 9(6).
-
- 02 ALB-FECHA PIC 9(8).
-
- 02 ALB-CLIENTE PIC 9(4).
-
- 02 ALB-ELEMENTOS OCCURS 10 TIMES.
-
-
- 03 ALB-PRODUCTO PIC 9(4).
-
-
-
- 03 ALB-CANTIDAD PIC 9(6)V99.
-
-
-
- 03 ALB-PRECIO PIC 9(6)V99.
-
FD CLIENTES LABEL RECORD STANDARD.
01 REGCLIEN.
-
- 02 CLI-CODIGO PIC 9(4).
-
- 02 CLI-NOMBRE PIC X(40).
-
- 02 CLI-DOMICILIO PIC X(40).
-
- 02 CLI-POBLACION PIC X(30).
-
- 02 CLI-PROVINCIA PIC X(20).
-
- 02 CLI-NIF PIC X(10).
FD PRODUCTOS LABEL RECORD STANDARD.
01 REGPRODU.
-
- 02 PRO-CODIGO PIC 9(4).
-
- 02 PRO-NOMBRE PIC X(40).
-
- 02 PRO-EXISTENCIAS PIC S9(6)V99.
-
- 02 PRO-PROVEEDOR PIC 9(4).
-
- 02 PRO-UNIMEDIDA PIC X(20).
FD IMPRE LABEL RECORD OMITTED.
01 LINEA PIC X(80).
……….. En cuanto a las variables que vamos a utilizar:
Las de estado de cada fichero.
Contadores para la tabla, las líneas y los albaranes leidos.
Las líneas de nuestro listado.
WORKING-STORAGE SECTION.
01 ESTADOS.
-
- 02 STA-ALBA PIC XX.
-
- 02 STA-CLIEN PIC XX.
-
- 02 STA-PRODU PIC XX.
01 CONTA1 PIC 9(6).
01 CONTALIN PIC 9(6).
01 CONTA PIC 9(6).
01 FINFIC PIC X.
01 TOTAL PIC S9(8)V99.
01 TOLINEA PIC S9(8)V99.
01 TOALBA PIC S9(8)V99.
01 LIN-01.
-
- 02 FILLER PIC X(66) VALUE ‘LISTADO DE ALBARANES’.
-
- 02 FILLER PIC X(10) VALUE ‘HOJA .. ‘.
-
- 02 L-HOJA PIC ZZZZ.
01 LIN-02.
-
- 02 FILLER PIC X(9) VALUE ‘ALBARAN’.
-
- 02 FILLER PIC X(12) VALUE ‘ FECHA’.
-
- 02 FILLER PIC X(46) VALUE ‘CLIENTE’.
-
- 02 FILLER PIC X(13) VALUE ‘IMPORTE’.
01 RAYA PIC X(80) VALUE ALL ‘=’.
01 DETALLE.
-
- 02 L-ALBARAN PIC ZZZ.ZZZBB.
-
- 02 L-FECHA PIC ZZ/ZZ/ZZZZBB.
-
- 02 L-CLIENTE PIC ZZZZB.
-
- 02 L-NOMCLI PIC X(40)B.
-
- 02 L-IMPORTE PIC ZZ.ZZZ.ZZ9,99.
Ahora es el momento de la programación, de que todo salga tal y como hemos planteado.
Voy a ir incluyendo líneas de comentario para que la explicación quede mas detallada. En pocas palabras leemos, actualizamos, listamos, terminamos.
PROCEDURE DIVISION.
INICIO.
-
- Abrimos los ficheros cada uno en su modo adecuado, imprimimos la cabecera del listado e inicializamos las variables.
OPEN INPUT ALBARANES CLIENTES I-O PRODUCTOS EXTEND IMPRE
PERFORM CABECERA
MOVE ‘ ‘ TO FINFIC MOVE 0 TO TOTAL CONTA
-
Iniciamos el bucle de lectura hasta que se cumpla la condición de fin de fichero y movemos campos del fichero a variables de la línea de impresión.
PERFORM UNTIL FINFIC = ‘S’
-
- READ ALBARANES NEXT RECORD AT END MOVE ‘S’ TO FINFIC
-
- NOT AT END
-
- MOVE ALB-CODIGO TO L-ALBARAN
-
- MOVE ALB-FECHA TO L-FECHA
-
-
- Nos preparamos para leer clientes, tomamos su nombre y si no existe ponemos una aclaración.
-
MOVE ALB-CLIENTE TO CLI-COD
READ CLIENTES INVALID KEY MOVE ‘NO EXISTE EL CLIENTE’ TO CLI-NOMBRE
END-READ
MOVE CLI-NOMBRE TO L-NOMCLI
-
-
Comenzamos otro bucle interno para acceder a los elementos de la tabla que contiene los productos, la cantidad y el precio del albarán. Además sumamos sus importes para totalizar el albaran. También, si el producto existe le quitamos la cantidad vendida de sus existencias y regrabamos.
-
MOVE 0 TO TOALBA
PERFORM VARYING CONTA1 FROM 1 BY 1 UNTIL CONTA1 > 10
-
- IF ALB-PRODUCTO (CONTA1) > 0
-
-
- MOVE ALB-PRODUCTO TO PRO-CODIGO
-
-
-
- READ PRODUCTO INVALID KEY
-
-
-
-
- DISPLAY ‘PRODUCTO NO ENCONTRADO, NO SE ACTUALIZA’
-
-
-
-
-
- NOT INVALID KEY
-
-
-
-
-
- SUBTRACT ALB-CANTIDAD FROM PRO-EXISTENCIAS
-
-
-
-
-
- REWRITE REGPRODU INVALID KEY DISPLAY ‘ERROR’
-
-
-
-
-
- END-REWRITE
-
-
-
-
END-READ
-
-
-
- COMPUTE TOLINEA ROUNDED = ALB-CANTIDAD * ALB-PRECIO
-
-
-
- COMPUTE TOALBA ROUNDED = TOALBA + TOLINEA
-
-
END-IF
END-PERFORM
-
-
Al totalizar el albaran imprimimos la linea correspondiente y comprobamos que no hemos llegado al final de la página. El salto lo realizaremos despues de imprimir 54 líneas.
-
MOVE TO-ALBA TO L-IMPORTE
COMPUTE TOTAL ROUNDED = TOTAL + TOALBA
MOVE 0 TO TOALBA
WRITE LINEA FROM DETALLE AFTER 1
ADD 1 TO CONTALIN ADD 1 TO CONTA
IF CONTALIN > 54 WRITE LINEA FROM RAYA AFTER 1
-
- PERFORM CABECERA
END-IF
END-READ
END-PERFORM
-
Una vez terminado el bucle de lectura. Imprimimos una línea con los totales acumulados, cerramos los ficheros y terminamos el programa.
WRITE LINEA FROM RAYA AFTER 1
MOVE CONTA TO L-ALBARAN
MOVE 0 TO L-FECHA L-CLIENTE
MOVE ‘TOTAL GENERAL’ TO L-NOMCLI
MOVE TOTAL TO L-IMPORTE
WRITE LINEA FROM DETALLE AFTER 1
WRITE LINEA FROM RAYA AFTER 1
CLOSE PRODUCTOS ALBARANES CLIENTES IMPRE
STOP RUN.
Este párrafo es el que utilizamos para escribir la cabecera, tanto al principio como cada vez que llenamos una página.
CABECERA.
-
- MOVE 0 TO CONTALIN ADD 1 TO HOJA MOVE HOJA TO LHOJA
-
- WRITE LINEA FROM LIN-01 AFTER PAGE
-
- WRITE LINEA FROM RAYA AFTER 2
-
- WRITE LINEA FROM LIN-02 AFTER 1
-
- WRITE LINEA FROM RAYA AFTER 1.
- ………..
Creo que con este pequeño programa de ejemplo, podéis comprender el funcionamiento de los ficheros.
Como habéis visto, hemos leido secuencialmente, hemos accedido directamente por código, hemos impreso líneas en una impresora y además hemos ido actualizando un fichero, con lo que todos los procesos que se pueden dar (mas o menos) han quedado reflejados.
Me gustaría comentar el tema de la apertura de ficheros.
Un archivo abierto como INPUT jamás tendrá exclusividad y podrá ser accedido por todos los usuarios que lo utilicen sin ningún problema de bloqueos. En cambio al abrirlo como I-O podemos tener problemas de bloqueos y además si se va la luz en ese momento o se apaga bruscamente el PC podemos llegar a ocasionar el estatus 98 y tener verdaderos problemas para recuperar el fichero.
Mi consejo particular, que es el que uso desde hace muchos años y jamás he tenido un problema ni de bloqueos ni de errores de ficheros, es abrir siempre el fichero como INPUT y solo en los momentos necesarios abrirlos como I-O. Nunca abrais los ficheros nada mas empezar el programa como I-O, no es necesario y en muchos casos solo nos va a traer problemas, al igual que habrá programas en los que no sea necesario tener un fichero abierto siempre y solo abrirlo cuando lo vayamos a utilizar.
Os repito que estos consejos son los que la experiencia me ha ido enseñando y hoy en día con los equipos informáticos que existen, la posible pérdida de tiempo en abrir y cerrar ficheros, pasa totalmente inadvertida para nosotros.
Para ver lo que os he explicado antes de una manera mas fácil, os pongo un ejemplo:
Empezamos nuestro programa y hacemos un:
OPEN INPUT FICHERO
…
…
-
- el programa sigue su curso, pero si en algún momento necesitamos Borrar, Grabar o Regrabar algún registro, entonces actuamos así:
…
CLOSE FICHERO OPEN I-O FICHERO
REWRITE REGISTRO INVALID KEY DISPLAY ‘ERROR’
END-REWRITE
CLOSE FICHERO OPEN INPUT FICHERO
…
…
Si en este caso, dos usuarios distintos estuvieran modificando el mismo registro y los dos le dieran a la vez a grabar, siempre se quedarán los datos del último que haya pulsado, pero jamás creará un problema de conflictos, ya que aunque los ordenadores sean muy rápidos, siempre tienen que llevar un orden en la ejecución de las instrucciones.
En la siguiente entrega, que será la última, veremos como asignar diferentes nombres a nuestros ficheros y como hacer un archivo secuencial para abrirlo con Excel, por ejemplo.