La Página de DriverOp

Archivos en rm/cobol 85.

En este artículo trataré de enseñar cómo se trabaja con archivos de datos COBOL usando el compilador RM/COBOL85.

Nota: los archivos con extensión .rm corresponden a la aplicación RealOne/Real Player y no tienen nada que ver con COBOL.

Consideraciones preliminares.

Hace tiempo que vengo rumeando la idea de poner por escrito lo que he aprendido acerca de archivos de datos en RM/COBOL 85 en mi larga relación con este compilador el cual me ha reportado grandes satisfacciones. Es verdad que el compilador que he estado usando todos estos años está definitivamente obsoleto a la fecha de escribir esto, sin embargo gracias a este mi sitio web y a las listas de correo en las que participo, me llega cada tanto algún mensaje de algún/a desesperado/a programador novel y no tan novel preguntándome sobre este tema en particular. Debido a esto he llegado a la conclusión de que apesar de los años de antigüedad de esta herramienta de programación, aún hay interés en él. Aunque más no sea por razones históricas, pues aquí vamos.

Debo dejar bien claro que este artículo se refiere únicamente para el compilador RM/COBOL 85 versión 5.36, que es la última versión para MS-DOS de la casa Liant (antes conocida como Ryan McFarland) ya que es el compilador que mejor conozco (con todo y sus bugs). No es mi intención describir ni entrar en los detalles sobre las diferencias con otros compiladores del mismo lenguaje y plataforma. Advierto al lector que si está usando una versión diferente o un compilador completamente diferente al que me refiero aquí tendrá que buscar por sí mismo cómo subsanar las diferencias que pudiera haber con lo que aquí escribo.

Asumo que el lector (o lectora) interesando en este artículo cuenta con conocimientos básicos de COBOL. Esto no es un tutorial de programación en COBOL y no explicaré las cosas más básicas de este lenguaje, cosa que ya he hecho, en parte, en otro lugar de mi sitio.

Exportar datos a otros lenguajes.

La mitad de las consultas que recibo a mi correo personal vienen de programadores de otros lenguajes que se ven en la necesidad de portar una aplicación COBOL a otro lenguaje usando las bases de datos de ese otro lenguaje DBM (seaéste DBASE, Clipper o Clarion) o a otro DBE (Data Base Engine, tal como SQL Server, BDE, Interbase/FireBird o MySQL) y se encuentran con la falta de herramientas para hacer esa exportación de datos. Considero que los programadores más jóvenes están acostumbrados a los formatos de datos"abiertos" que esos lenguajes y DBMs tienen mientras que éste compilador (como así otros compiladores de COBOL de la misma época) se basan en un formato de tipo "cerrado" donde es el propio programador quien establece el formato interno de los archivos que va a usar. Lejos de entraren la disputa sobre qué método es mejor, mi respuesta a todos ellos es esta: si no cuentan con la definición de registros (FD), el tipo de encabezado del archivo (SELECT) y la versión con que fue creado el programa COBOL que genera dichos archivos me temo que tendrás que "hackear" de alguna forma la aplicación COBOL que ya está andando para poder extraer al menos una parte de los campos de los mismos. Claro que existe la posibilidad de comprar a Liant una herramienta de volcado de datos aunque este sería el último recurso.

A pesar de lo dicho anteriormente sí existen algunas herramientas que sin ser 100% efectiva posibilitan hacer un volcado de los datos a un formato abierto.

Específicamente, si hablamos de una aplicación RM/COBOL existen tres formatos básicos sobre los que un programador puede armar su base de datos, numeradas de la 1 a la 3. La versión 1 la usan las versiones de la 1 a la 4 del RM/COBOL 85. La versión 2 la usan las versiones de la 4 a la 5.36. Mientras que la última es usada por los compiladores orientados a objetos y visuales de COBOL hechos por la casa Liant. Hasta donde sé, todas las versiones del RM/COBOL mantienen compatibilidad hacia atrás pero no hacia adelante. Esto quiere decir que si contamos con un compilador que usa la versión 2 de los archivos de datos COBOL podemos leerlos de la versión 1, pero el caso inverso no se da.

Cuando he dicho "hackear" la aplicación RM/COBOL 85 quiero decir hacer cosas como ejecutar una salida por impresora, si es que la aplicación cuenta con ella, y redireccionar la salida de datos hacia un archivo de texto, cosa facil de hacer en MS-DOS y más facil aún ejecutando la aplicación sobre Windows 95/98, aunque luego requiera reformatear el archivo resultante, eso es mejor que nada (esta aplicación podría resultar útil en casos como el mencionado).

Tipos de organizaciones.

En COBOL existen tres tipos de organizaciones básicas para sus archivos de datos. Estos son:

  • Secuencial.
  • Relativa.
  • Secuencial indexada.

El orden mencionado es también el orden de complejidad de los archivos donde "complejo" en este caso quiere decir complejidad estructural del archivo y no en sus métodos de acceso, como veremos más adelante.

Secuencial.

Es el tipo más sencillo y es en la práctica, equivalente a un archivo de texto donde cada línea sería un registro. La forma de acceso (lectura, escritura), como su nombre indica es secuencial. La diferenciacon un archivo de texto normal está dada en el tamaño del registro. Mientras que en un archivo de texto cada línea está separada por un retorno de carro/avance de línea (dependiendo del S.O.)y éstos pueden caer en cualquier parte del archivo haciendo que el tamañode las líneas sean variables, en COBOL el tamaño de las líneases fijo y ésta está dada por la suma de los tamaños delos campos que componen dicho registro. Esto permite que se puedanleerarchivos secuenciales hacia atrás o insertar registros enmedio de otros dos yaexistentes. La desventaja es que no contamos con índices para reordenarlas vistas de los datos. En cada lectura del archivo los datos aparecen en elorden en que se encuentran ordenados físicamente en el archivo.

Otra forma de referirse a este tipo de organización es archivos de acceso directo en contraposición a archivos de acceso aleatorio, ya que, parallegar a un registro en particular es necesario pasar por todos los anteriorestanto hacia adelante como hacia atrás.

Relativa.

Esto es un avance respecto de la organización anterior ya que contamos con un índice para efectuar las lecturas o escrituras del archivo aunque este índice está dado exclusivamente por la posición relativa del registro. Cada registro cuenta con un índice implícito dado, como mencioné, por su posición en relación al inicio del archivo. Este tipo de organización es familiar para los programadores en Pascal y otros lenguajes de propósito general.

Aquí ya no es necesario pasar por todos los registros anteriores al que nos interesa leer o reescribir, basta con indicar su posición relativa. Por ejemplo, si quisiéramos acceder al décimo registro del archivo el puntero avanzaría nueve posiciones y leería la décima. De igual manera, si qusiéramos leer el quinto solamente hace falta restar cuatro posiciones. Cuánto espacio hay que sumar y restar para accedera un registro en particular está dado por el tamaño declarado del registro que no es más que la suma de los tamaños de los campos que lo componen.

Secuencial indexada.

Éste tipo de organización es lo que el común de los programadores entienden como un verdadero archivo de bases de datos. Aquí ya contamos con uno o varios índices que mantienen un orden lógico de los datos contenidos independientemente de su posición relativa dentro del archivo. Lo que sorprenderá a algunos programadores aquí es que RM/COBOL 85 mantiene esos índices dentro del propio archivo de datos y no como uno o varios archivos externos. Sobre cómo consigue esto el compilador y su motor de bases de datos internos escapa a los propósitos de este artículo, solo baste decir que para lograrlo usa una técnica similar a una estructura de árbol dinámica.

La ventaja de tener los índices físicamente en el mismo archivo que los datos recide en que no se corre el riesgo de perder esa indexación, de esta forma no se necesita hacer una reindexación previa al uso del archivo (programadores de Clipper ¿les suena esto de alguna parte?).

El o los índices de un archivo con organización secuencial indexada puede ser cualquiera de los campos declarados en el registro aunque con la salvedad de que una vez establecido uno o más de esos campos estos permanecen fijos, es decir que posteriormente no se pueden usar otros campos además de los declarados. Esta declaración se hace en tiempo de edición haciendo uso de las cláusulas que COBOL proporciona para este propósito, como veremos más adelante.

Esto último representa una desventaja respecto a otros mecanismos de indexación. Es obvio que acceder a un conjunto de registros ordenados por un campo índice en particular (lo que en la jerga de base de datos relacionales se llama "vista") es mucho más rápido que hacerlo secuencialmente o relativamente, pero como esos campos índices se establecen al momento de diseñar la base de datos y una vez creado el o los archivos físicos ya no se pueden agregar más o modificarlos ya existentes un programador COBOL debe tener muy en claro qué tipo de vistas necesita extraer de esa base de datos de acuerdo a la aplicaciónque está programado. Una aplicación COBOL con los índices mal elegidos puede resultar en una aplicación lenta o directamente inútil.

Modos de accesos.

En la sección anterior vimos los tipos de organización que pueden establecerse con los archivos COBOL. Hemos visto además que esa organización queda establecida al momento del diseño y permanece así durante toda la vida del archivo. Ahora voy a dar revista a los modos en que esos archivos pueden accederse según su organización.

Estos son:

Acceso secuencial.

Como mencioné anteriormente este tipo de acceso se refiere a cuando se va accediendo a los registros sucesivamente uno detrás del otro. Este modo de acceso es el único permitido para archivos con organización secuencial.

Acceso directo o aleatorio.

Aquí el modo ya no es secuencial sino que para leer o escribir en un registro particular basta con indicar la posición relativa o indexadade ese registro dentro del archivo sin necesidad de pasar por todos los anteriores. Este modo de acceso está permitido para archivos con organización relativa y organización secuencial indexada.

Para el caso de archivos con organización relativa hay que especificar qué variable almacenará el puntero del archivo. Esta variable no puede ser un campo del propio archivo. Si el modo de acceso es secuencial se trata el archivo como si fuera de organización secuencial.

Mientras que para el caso de los archivos con organización secuencial indexada se debe usar cualquiera de los campos índices declarados.

Implementación.

En este artículo implementaré en RM/COBOL 85 la organización secuencial indexada con acceso directo. Pasaré de largo la organización relativa ya que queda obsoleta frente a este otro modo, mientras que hacia el final comentaré brevemente la implementación de la organización secuencial que a pesar de su humildad resulta útil cuando no se necesita la sofisticación de un acceso basado en índices.

Declaración del encabezado (SELECT).

Para comenzar hay que declarar el tipo de organización y modo de acceso. Esto se realiza en la ENVIRONMENT DIVISION, sección INPUT-OUTPUT SECTION, párrafo FILE-CONTROL usando la sentencia SELECT la cual todo su párrafo debe estar identado en la columa 12. La sintaxis general de la sentencia SELECT es como sigue:

Sintaxis SELECT
SELECT [OPTIONAL] nombre-del-archivo-lógico ASSIGN TO nombre-del-archivo-físico
ORGANIZATION IS tipo-de-organización
ACCESS MODE modo-de-acceso
RECORD KEY campo-indice-primario
[ALTERNATE RECORD KEY campo-indice-secuntario [WITH DUPLICATES]]

Las cláusulas encerradas entre corchetes rectos indican cláusulas opcionales. La cláusula OPTIONAL le indica al compilador que en caso de que el archivo nombre-del-archivo-físico no exista al momento de intentar abrirlo, lo cree. La cláusula ALTERNATE RECORD KEY indica la clave secundaria que será usada en el archivo, ésta cláusula se puede repetir más de una vez siempre que en cada caso se indique un campo del archivo diferente o combinación no ambigüa de ellos. A su vez esta cláusula pose e otra cláusula también opcional WITH DUPLICATES que indica los valores para esos campos pueden estar repetidos en el archivos. El índice primario del archivo no puede contener valores duplicados en el archivo.

El literal nombre-del-archivo-lógico es un identificador arbitrario que se usará para referirse lógicamente al archivo durante la PROCEDURE DIVISION, mientras que nombre-de-archivo-físico es el nombre aceptado por el sistema operativo para ese archivo. Hay que tener en cuenta que RM/COBOL 85 ver. 5.36 fue diseñado para MS-DOS y el nombre físico del archivo debe ser compatible con este sistema operativo.

La cláusula ORGANIZATION declara el tipo de organización del archivo, el cual puede ser:

  • SEQUENTIAL
  • RELATIVE
  • INDEXED

Cada una se corresponde con lo ya explicado. Más adelante en este artículo veremos una pequeña modificación al tipo SEQUENTIAL.

La cláusula ACCESS MODE puede ser uno de estos:

  • SEQUENTIAL
  • RANDOM
  • DYNAMIC

Si el archivo tiene organización INDEXED y luego se declara modo de acceso SEQUENTIAL el archivo se trata como si tuviera organización SEQUENTIAL. Si el modo de acceso es RANDOM cada vez que se haga una lectura o escritura en el archivo primero hay que proporcionar un valor de la clave primaria obligatoriamente pues de esa forma el compilador sabe qué registro leer y escribir. Finalmente si el modo de acceso es DYNAMIC basta con proporcionar un valor aproximado de la clave primaria (o secundaria en el caso de que se delcaren cláusulas ALTERNATE RECORD KEY). Por lo que el acceso DYNAMIC es el más versatil de los tres.

Debo hacer notar un hecho importante. El tipo de organización queda plasmado físicamente en el archivo, como se explicó anteriormente, no así el modo de acceso. Esto significa que un archivo declarado con organización INDEXED puede ser accedido por cualquiera de los tres modos de acceso. Para el caso de un archivo con organización SEQUENTIAL no es necesario especificar el modo de acceso ya que solo permite uno: el secuencial.

El campo que se especifica en la cláusula RECORD KEY debe existir como identificador en la declaración del registro del archivo, el cual veremos más adelante, como así tambien el o los campos de las cláusulas optativas ALTERNATE RECORD KEY.

Veamos un ejemplo real de declaración de archivo:

Ejemplo de SELECT
        SELECT OPTIONAL CLIENTES ASSIGN TO "CLIENTES.DAT"
        ORGANIZATION IS INDEXED

ACCESS MODE DYNAMIC

RECORD KEY CODIGO-CLIENTE ALTERNATE RECORD KEY APELLIDO-NOMBRE WITH DUPLICATES.

Aquí he declarado un archivo con nombre lógico CLIENTES y nombre físico "CLIENTES.DAT", organización secuencial indexada. Usaré el archivo con modo de acceso DYNAMIC, el índice principal del archivo será el campo CODIGO-CLIENTE y he declarado ademásuna clave secundaria formada por el campo APELLIDO-NOMBRE el cual puede contener valores duplicados.

Declaración del registro (FD).

Pasemos ahora a la declaración del registro del archivo. Esta se hace en la división DATA DIVISION, sección FILE SECTION, párrafo FD ("FD" viene por "File Definition" o "Definición de Archivo").

La sintaxis general de la cláusula FD es como sigue:

Sintaxis de FD
        FD nombre-del-archivo-lógico
        [RECORD tamaño-del-registro CHARACTERS]
            [BLOCK tamaño-del-bloque CHARACTERS/RECORDS]
            [LABEL RECORD tipo-de-etiqueta]
            [DATA RECORD identificador].

Como se puede ver no todas las cláusulas son obligatorias, la única obligatoria es la propia definición FD (que designa un párrafo en sí mismo) a la cual se le debe seguir el nombre del archivo lógico (tal como se especificó en el párrafo SELECT) para el cual estamos definiendo su registro.

RECORD es opcional pero útil, le indica al compilador cuántos bytes ocupa un registro del archivo. Si esta cláusula se omite el compilador calcula la suma de los tamaños de los campos del registro y ese seráel tamaño final del registro físico. En cambio si se proporcionaun tamaño, el cual debe ser un entero positivo, éste no puede ser menor a esa suma. Si el tamaño indicado explícitamente es mayor a la suma de los tamaños de los campos, el compilador llenaráel espacio sobrante con espacios en blanco. Aquí está la utilidad de especificar un tamaño físico para el registro pues permite que en el futuro se le puedan ir agregando más campos no indicados inicialmente. Incluso, con una pequeña variación en la declaración de esta cláusula es posible declarar un tamaño variable para el registro de la siguiente manera:

...
           RECORD tamaño-mínimo TO tamaño-máximo CHARACTERS
...

Siendo tamaño-mínimo y tamaño-máximo enteros positivos.

La cláusula BLOCK es también opcional. Su utilidad consiste en especificar cuántos bytes (CHARACTERS) o registros físicos (RECORD) serán tratados en bloque por el compilador por cada acceso al archivo. Esto permite una optimización en la eficiencia en el accesoa disco y está relacionado con el tamaño de los sectores de disco que maneja el sistema operativo. Suponiendo que estamos trabajando sobre un disco formateado con el sistema de archivos FAT cuyos sectores tienen un tamañode 2048 bytes y suponiendo que la suma de los tamaños de los campos del registro de nuestro archivo es de 512 bytes podemos ver que por cada sector del disco caben 4 registros. Como el sistema operativo lee del disco un sector completo, si especificamos (o para el caso omitimos) un BLOCK de igual tamaño que el tamaño del registro del archivo expresado en bytes, cada vez que se haga una lectura secuencial (es decir, registros físicamente contiguos) del archivo, el sistema operativo hará cuatro lecturas del mismo sector para entregarle al programa los cuatro registros. BLOCK hace que el compilador reserve espacio de memoria para esas lecturas, de modo tal que si en vez de especificar un bloque igual al tamaño del registro, especificamos un bloque de tamaño igual al del sector físico del disco, el sistema operativo entregará cuatro registros de una sola vez, acelerando enormemente el proceso ya que para la próxima lectura secuencial de un registro ese registro estará en el espacio de memoria reservado por el compilador.

Jugar con los tamaños de las cláusulas RECORD y BLOCK puede impactar enormemente en el rendimiento de la base de datos.

La cláusula BLOCK puede especificarse tanto en bytes como en cantidad de registros, en este último caso el tamaño final en bytes será el producto del tamaño del registro multiplicado por la cantidad de registros especificados. Sabiendo esto resultará evidente que si especificamos un bloque demasiado grande podemos agotar la memoria de la computadora.

La cláusula LABEL RECORD, también opcional, le indica al compilador que al momento de crear el archivo físico agregue unas etiquetas especiales al principio y al final del archivo para poder identificar correctamente el tipo de archivo del que se trata. Esto es solo relevante para archivos que se almacenan en disco (y a decir verdad se trata de un anacronismo, de la época en que se usaban cintas magnéticas las cuales necesitan de registros especiales llamadas "de inicio y parada"). Esta cláusula puede tomar dos valores. OMITTED hace que no se creen estas etiquetas, y STANDARD hace que se creen las etiquetas según el tipo de almacenamiento encontrado.

Por último, la cláusula DATA RECORD indica cuáles el nombre lógico del registro para ese archivo. Si esta cláusula se omite, puesto que es opcional, el registro lógico debe hallarse declarado inmediatamente después del párrafo FD. Si se especifica nos dala posibilidad de referenciar más de un registro lógico para el archivo (separando los identificadores con espacio). Si este es el caso entonces el valor de la cláusula RECORD debe ser la suma de todos los registros lógicos especificados en ésta cláusula (y tambiénpara la cláusula BLOCK).

Continuando con el ejemplo del archivo CLIENTES, esta sería una declaración FD típica:

Ejemplo de FD
       FD CLIENTES
           RECORD 128 CHARACTERS
           BLOCK 512 CHARACTERS
           DATA RECORD REG-CLIE.

       01 REG-CLIE.
         02 CODIGO-CLIENTE PIC 9(5).
         02 APELLIDO-NOMBRE.
           03 APELLIDO PIC X(20).
           03 NOMBRE PIC X(20).

He declarado un registro con tamaño 128 bytes, bloque de lectura de 512 bytes (es decir 4 registros), el registro lógico será REG-CLIE el cual declaro inmediatamente a continuación.

Puede verse que la suma de los campos del registro lógico suman 45 que está bien por debajo de los 128 declarados en la cláusula RECORD. Notar además que los identificadores de los campos del registros soncorrespondientes a los usados en la delaración SELECT más arriba.

He decidido separar el campo APELLIDO-NOMBRE en dos campos dependientes para facilitar la ordenación de la clave alternativa. Hablando del ejemplo concreto, al haber declarado el índice alternativo con la cláusula WITH DUPLICATES me permite tener dos clientes con el mismo nombre y apellido, esto es válido en la vida real puesto que es posible que dos personas compartan el mismo nombre. Para nuestro hipotético sistema quedarían almacenados como dos clientes distintos, cada uno con su código de cliente.

Sentencias para la manipulación de Archivos.

Antes de comenzar a tratar el tema de la manipulación de archivos, quisiera completar el ejemplo que he estado armando para este artículo poniendo a continuación el código incompleto aún, pero válido (es decir, el compilador lo acepta), que me servirá para explicar mejor el uso de las sentencias de manipulación de archivos:

Ejemplo funcional (incompleto)
       IDENTIFICATION DIVISION.
       PROGRAM-ID. EJEMPLO.
       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT OPTIONAL CLIENTES ASSIGN TO "CLIENTES.DAT"
           ORGANIZATION IS INDEXED
           ACCESS MODE DYNAMIC
           RECORD KEY CODIGO-CLIENTE
           ALTERNATE RECORD KEY APELLIDO-NOMBRE WITH DUPLICATES.

       DATA DIVISION.
       FILE SECTION.
       FD CLIENTES
           RECORD 128 CHARACTERS
           BLOCK 512 CHARACTERS
           DATA RECORD REG-CLIE.

       01 REG-CLIE.
         02 CODIGO-CLIENTE PIC 9(5).
         02 APELLIDO-NOMBRE.
           03 APELLIDO PIC X(20).
           03 NOMBRE PIC X(20).

       WORKING-STORAGE SECTION.
       77 ESPERA PIC X.
       77 FLAG PIC X.
       PROCEDURE DIVISION.
       COMIENZO.
           ...

He agregado lo que faltaba para hacer un programa funcional y he declarado un par de variables de trabajo.

OPEN.

Antes de manipular un archivo hay que abrirlo. De esto se encarga la sentencia OPEN. El cual requiere un modo de apertura según el tratamiento que se le va a dar al archivo en cuestión. Los modos pueden ser:

INPUT: el archivo será usado para leer registros únicamente.

OUTPUT: el archivo será escrito con registros pero esta cláusula de la sentencia OPEN si se usa en un archivo que ya existe físicamente causa que todo el contenido previo desaparezca, es decir, el archivo queda concero registros luego de la apertura.

I-O: el archivo será usado tanto para lectura como escritura.

Excepto por la cláusula OUTPUT, si se intenta abrir un archivo en modo INPUT o I-O y éste no existe físicamente se elevará una excepción. Esto puede ser evitado agregando la cláusula OPTIONAL en la declaración SELECT, tal como vimos anteriormente.

A continuación de esta cláusula debe especificarse el nombre lógico del archivo que se intenta abrir.

Entonces la sintaxis general de la sentencia OPEN es:

Sintaxis de OPEN
OPEN INPUT/OUTPUT/I-O nombre-logico-del-archivo.

Y el ejemplo concreto para nuestro archivo CLIENTES sería:

Ejemplo de OPEN
           OPEN I-O CLIENTES.

En este caso vamos a trabajar tanto para leer como para escribir en el archivo.

CLOSE.

Luego de trabajar con un archivo hay que cerrarlo. La utilidad de esta sentencia está relacionada con las actualizaciones necesarias que debe efectuar el compilador sobre el archivo en cuestión además de liberar todos los buffers y apuntadores que creó para poder manipular el archivo. Si bien el compilador (mejor dicho, el runtime) lleva la cuenta de los archivos abiertos por el programa y los cierra automáticamente luego que se abandona la ejecución del mismo, no hay que confiar esta tarea al compilador.

De hecho mi recomendación personal es que nunca se mantengan archivos abiertos que ya no se van a usar y a su vez no abrir archivos que se sabe no van a ser usados. Esto se debe a por el modo en que COBOL mantiene los índices de los archivos de datos explicado más arriba, si se tiene un archivo (o muchos) abierto y ocurre un imprevisto, como un corte de electricidad, el arbol de índices de los archivos abiertos podría dañarse, a veces irreparablemente (RM/COBOL tiene una utilidad para reparar índices pero no es 100% efectiva).

Por otro lado el propio sistema operativo puede tener un límite en la cantidad de archivos abiertos que un programa cualquiera puede mantener (en MS-DOS esto se especifica en el CONFIG.SYS mediante la directiva FILES).

La sintaxis de la sentencia CLOSE es sumamente sencilla:

Sintaxis de CLOSE
CLOSE nombre-lógico-del-archivo.

Y en nuestro ejemplo sería:

Ejemplo de CLOSE
           CLOSE CLIENTES.

Hay cuatro operaciones básicas que se pueden realizar sobre un archivo. Leer. Escribir. Sobreescribir. Y borrar. Todas referidas a registros individuales. El comportamiento de cada una de estas operaciones en COBOL está ligado al tipo de organización del archivo.

READ.

La sentencia READ sirve para leer un registro del archivo siempre que el archivo no haya sido abierto en modo OUTPUT. Ésta viene en dos "sabores"dependiendo del modo de acceso, ACCESS MODE, que fue declarado en el encabezado.

Si el ACCESS MODE es SEQUENTIAL o bien DYNAMIC, la sintaxis es:

Sintaxis de READ con ACCESS MODE SEQUENTIAL o DYNAMIC
READ nombre-lógico-del-archivo [NEXT/PREVIOUS [AT END sentencia]].

Como siempre, lo que está entre corchetes rectos es opcional. Si se omite NEXT y PREVIOUS se asume NEXT que significa que por cada lectura el puntero se avanza al siguiente registro, según el orden del índice empleado para hacer la lectura en el caso de que el ACCESS MODE sea DYNAMIC o al siguiente registro físico en el caso de que el ACCESS MODE sea SEQUENTIAL. Mientras que PREVIOUS es lo opuesto a NEXT, es decir, sirve para hacer lecturas en reversa. La cláusula AT END sirve para indicar qué debe hacerse cuando se encuentra el final del archivo (o el principio para lecturas PREVIOUS) por lo tanto debe ser seguida de una sentencia(¿CLOSE tal vez?).

Ahora bien, la pregunta lógica que el lector se debe estar haciendoes ¿cómo sabe READ qué índice debe seguir para hacerlas lecturas en el caso de que el archivo tenga declarado más de un índice?. O peor aún, ¿cómo sabe cuál es el índice que debe usar para comenzar a leer y desde dónde comenzar a leer?. Estas preguntas las responderé cuando veamos la sentencia START. Por ahora baste decir que si el archivo está declarado con ACCESS MODE DYNAMIC el valor de la clave que se usa para hacer las lecturas (y las otras operaciones que veremos a continuación) deben tener un valor concreto. Más sobre este punto más adelante.

Ahora veamos el segundo "sabor" de READ el cual se usa cuando el ACCESS MODE del archivo es RANDOM:

Sintaxis de READ con ACCESS MODE RANDOM
READ nombre-lógico-del-archivo [[NOT] INVALID KEY sentencia].

Como comentaba cuando expuse el tema de los modos de acceso, cuando el ACCESS MODE es RANDOM solo se puede usar el índice primario del archivo, es decir el campo que está a continuación de la declaración RECORD KEY en la sentencia SELECT. Ese campo debe tener un valor concreto previo a la lectura del archivo. La cláusula INVALID KEY indica qué debe hacerse en caso de que no exista ningún registro cuyo campo clave contenga el valor asignado previamente para ese campo clave. Esta cláusula puede ser negada anteponiendo la palabra NOT, en ese caso la sentencia se ejecutará si se encontró un registro que contiene el valor asignado a la clave primaria.

Si no se especifica la cláusula INVALID KEY y se encuentra un registro que sí contiene el valor asignado a la clave primaria, se continua la ejecución con la sentencia siguiente a READ. Pero, si no se encuentraun registro concordante el programa aborta con una excepción de entrada-salida (concretamente la número 23).

Usando nuestro ejemplo de CLIENTES.

Ejemplo de READ
           OPEN INPUT CLIENTES.
           MOVE 1 TO CODIGO-CLIENTE.
           READ CLIENTES.
           CLOSE CLIENTES.

En este primer ejemplo comenzamos abriendo el archivo para lectura. Asignamos un valor para el campo índice primario, intentamos leer el archivo que buscará un registro cuyo campo contenga el valor asignado. Como no especificamosla cláusula INVALID KEY en el READ podría causar una excepción de entrada-salida. Luego cerramos el archivo.

Ahora veamos un ejemplo más funcional.

Ejemplo de READ con INVALID KEY
           OPEN INPUT CLIENTES.
           MOVE 1 TO CODIGO-CLIENTE.
           READ CLIENTES INVALID KEY DISPLAY "Registro no encontrado".
           CLOSE CLIENTES.

Aquí a continuación de la cláusula INVALID KEY mostramos un mensaje en caso de que el registro 1 no haya sido encontrado. En cierta manera el READ funciona como una pregunta. Le pregunta al archivo ¿existe un registro cuya clave primaria es igual a 1?. Si la respuesta es afirmativa el registro es transferido al registro lógico del archivo, que para nuestro ejemplo es REG-CLIE, caso contrario se ejecuta la sentencia especificada a continuación de INVALID KEY.

Ahora consideremos el siguiente ejemplo asumiendo que el archivo CLIENTES contiene unos cuantos registros:

Ciclo de lectura con acceso RANDOM
       COMIENZO.
           OPEN INPUT CLIENTES.
           MOVE 1 TO CODIGO-CLIENTE.
       LEER.
           READ CLIENTES INVALID KEY GO CERRAR.
           DISPLAY CODIGO-CLIENTE.
           DISPLAY APELLIDO.
           DISPLAY NOMBRE.
           ADD 1 TO CODIGO-CLIENTE.
           GO LEER.
       CERRAR.
           CLOSE CLIENTES.
       SALIR.
           EXIT PROGRAM.
           STOP RUN.

Comenzamos abriendo el archivo. Establecemos el valor de la clave primaria. Intentamos una lectura. Que si fue exitosa mostramos los valores del resto delos campos leídos. Sumamos uno a la clave primaria y volvemos a leer.

Si el archivo contiene registros cuya clave primara tiene valores sucesivos tal como 1, 2, 3 y 4 habremos leído exitósamente de principio a fin todo el archivo, ¿cierto?, cierto. Pero qué sucedería en caso de que el archivo tenga registros cuya clave primaria es 1, 2, 3 y 5. Cuando intentemos la lectura del registro 4 saltará una excepción que hará que el flujo de ejecución continue en el párrafo CERRAR, cerrando el archivo y culminando la ejecución. Y el registro número 5 nunca se leerá.

De esto podemos deducir que usar READ en modo RANDOM sirve en aquellos casos en los que estamos seguros de encontrar el registro que queremos leer, o bien cuando queremos verificar que un determinado registro exista en el archivo. Por ejemplo si el usuario quisiera saber el nombre del cliente número 5 basta con pedirle al usuario que ingrese ese número en el campo CODIGO-CLIENTE, procedemos a leer el archivo y en caso de que exista mostrar sus datos, caso contrario (cláusula INVALID KEY) mostrar un mensaje acorde.

Bien pues, pero qué tal si quisiéramos leer un grupo de registros cuya clave primara no sabemos con anticipación. En ese caso debemos usar el primer "sabor" de la sentencia READ en conjunción con la sentencia START.

START.

Esta sentencia permite iniciar el proceso de acceso al archivo cuya organización sea INDEXED (o relativa) por cualquiera de sus índices (no necesariamente la primaria, como veremos a continuación) y sin necesidad de que el valor inicial del índice elejido exista en el archivo; y además el ACCESS MODE sea DYNAMIC.

La sintaxis general de START es:

Sintaxis de START
START nombre-lógico-del-archivo [KEY [NOT] condicional nombre-índice 
[[NOT] INVALID KEY sentencia]]]

Si no se especifica ninguna de las cláusulas opcionales, START no tiene efecto sobre el archivo (excepto si el archivo está vacío, en ese caso causará una excepción). START funciona de esta manera: se evalúa condicional contra el valor actual de nombre-índice, el cual debe ser cualquiera de los campos declarados como índices en la SELECT, sea primario o cualquiera de los secundarios; y el puntero del archivo se posiciona en el primer registro que satisface la condición. Condición es un operador de comparación tal como los símbolos = (igual a) > (mayor que) o < (menor que) o combinación de ellos, excepto <> (distinto que), en reemplazo de éste se usa la negación indicada por la palabra NOT que antecede a la condición (o sea para indicar "distinto que" se usa "NOT =").

Si ningún registro del archivo satisface la condición se provoca una excepción de entrada-salida análogo a lo ya visto para la sentencia READ de allí que es opcional la cláusula INVALID KEY y su negación.

Acepto que todo esto es un poco confuso a primera vista así que vamos a ver un ejemplo práctico, de paso resolveremos el problema que dejamos pendiente en la sección anterior.

Ejemplo de START
       COMIENZO.
           OPEN INPUT CLIENTES.
           MOVE 1 TO CODIGO-CLIENTE.
           START CLIENTES KEY NOT < CODIGO-CLIENTE.
       LEER.
           READ CLIENTES NEXT AT END GO CERRAR.
           DISPLAY CODIGO-CLIENTE.
           DISPLAY APELLIDO.
           DISPLAY NOMBRE.
           GO LEER.
       CERRAR.
           CLOSE CLIENTES.
       SALIR.
           ACCEPT ESPERA NO BEEP.
           EXIT PROGRAM.
           STOP RUN.

Aquí comenzamos abriendo el archivo. Asignamos valor 1 para el campo índice primario. Ejecutamos un START el cual dice que debe buscarse el primer registro que no sea menor a CODIGO-CLIENTE, o lo que es lo mismo, que no sea menor a 1, o... lo que es lo mismo que sea mayor o igual a 1. En nuestro caso ese registro podría ser cualquiera del archivo excepto el cero, pero el primero que cumple la condición es precisamente el 1. Si no existiera el 1 sería el 2 y así sucesivamente.

En caso de que el archivo esté vacío ningún registro cumple la condición, por lo que se provocará una excepción que en nuestro caso no está controlada con la cláusula INVALID KEY en el START.

Luego procedemos a la lectura del archivo secuencialmente. Esta lectura se hace siguiendo el valor ascendente de la clave primaria puesto que ese campo es el que se especificó en la sentencia START. Así hasta el final del archivo.

Si hubiésemos asignado el valor 3 a CODIGO-CLIENTE el registro tres cumpliría la condición. En nuestro hipotético caso de que no existiera el registro 4 en el archivo y le asignáramos ese valor a CODIGO-CLIENTE antes del START, el registro 5 cumpliría la condición.

START no solo funciona con el índice primario, también sirve con cualquiera de los índices secundarios. En nuestor ejemplo ese índice es el campo APELLIDO-NOMBRE que a su vez está permitido que sea duplicado, es decir que puede haber dos registros cuyos campos APELLIDO-NOMBRE tienen el mismo valor.

El siguiente ejemplo sería un ejemplo de listar el archivo de clientes alfabéticamente por APELLIDO-NOMBRE.

Ejemplo START por índice secundario
       COMIENZO.
           OPEN INPUT CLIENTES.
           MOVE " " TO APELLIDO-NOMBRE.
           START CLIENTES KEY NOT < APELLIDO-NOMBRE
           INVALID KEY GO CERRAR.
       LEER.
           READ CLIENTES NEXT AT END GO CERRAR.
           DISPLAY CODIGO-CLIENTE.
           DISPLAY APELLIDO.
           DISPLAY NOMBRE.
           GO LEER.
       CERRAR.
           CLOSE CLIENTES.
       SALIR.
           ACCEPT ESPERA NO BEEP.
           EXIT PROGRAM.
           STOP RUN.

Aquí lo que hacemos es asignar un espacio vacío al campo APELLIDO-NOMBRE y en el START usar ese campo como nombre-índice usando a su vez el mismo condicional ya visto. Lo que provoca que el archivo sea mostrado en pantalla ordenado alfabéticamente por ese campo, ya que el espacio es menor a la letra A. Incluso si se da el caso de que APELLIDO-NOMBRE esté vacío en alguno de los registros ese será el primero en ser listado.

Por supuesto, variando la condición y el valor asignado para el campo índice se puede listar el archivo de diferente manera. Y también se puede crear una condición que no puede ser satisfecha por ningún registro. Por ejemplo quitando la palabra NOT en el ejemplo podemos ver que no existe y no puede existir un registro que sea menor a espacio. Asímismo he agregado la cláusula INVALID KEY para el caso de que el archivo esté vacío (un archivo sin registros no satisface ninguna condición en START).

START no solo funciona de esta manera en conjunción con la sentencia READ, otras sentencias de manipulación de archivos también obedencen a START tal como se explicó hasta ahora.

WRITE.

Esta sentencia es opuesta a READ, es decir, escribe un registro en el archivo por lo que no se puede usar en archivos abiertos en modo INPUT:

Sintaxis de WRITE
WRITE nombre-del-registro [[NOT] INVALID KEY sentencia].

Notar que se usa el nombre del registro del archivo y no el nombre lógico del archivo.

Si se especifica la cláusula INVALID KEY ésta funciona de manera inversa, es decir salta una excepción de entrada-salida si el registro que se intenta escribir ya existe en el archivo. Esto sucede cuando el registro que se intenta agregar al archivo lleva en el campo declarado como índice primario un valor igual al de otro registro que ya existe en el archivo (por ejemplo, intentar escribir un nuevo registro con CODIGO-CLIENTE igual a 1 cuando ya existe un registro con ese valor). O bien, intentar escribir un nuevo registro en el que alguno de sus campos declarados como índices secundarios y ese campo no está declarado con WITH DUPLICATES ya existe en el archivo, es decir no se permite la duplicación de índices secundarios (el índice primario nunca se permite duplicado).

La sintaxis de WRITE explicada aquí es válida para archivos con organización INDEXED en modos de accesos RANDOM, DYNAMIC y SEQUENTIAL.

Muestro a continuación un ejemplo de uso de WRITE en conjuncióncon READ para cargar un registro a nuestro archivo de CLIENTES.

Ejemplo de WRITE
       COMIENZO.
           DISPLAY "Ingrese el código de cliente: ".
           ACCEPT CODIGO-CLIENTE NO BEEP PROMPT.
           MOVE " " TO FLAG.
           OPEN INPUT CLIENTES.
           READ CLIENTES INVALID KEY MOVE "I" TO FLAG.
           CLOSE CLIENTES.
           IF FLAG = " " THEN
             DISPLAY "Ya existe este código."
             GO COMIENZO.
           DISPLAY "Ingrese el Nombre de cliente: ".
           ACCEPT NOMBRE NO BEEP PROMPT.
           DISPLAY "Ingrese el Apellido de cliente: ".
           ACCEPT APELLIDO NO BEEP PROMPT.
           OPEN I-O CLIENTES.
           WRITE REG-CLIE.
           CLOSE CLIENTES.
       SALIR.
           EXIT PROGRAM.
           STOP RUN.

En este ejemplo comienzo pidiéndole al usuario que ingrese un código de cliente. Pongo una variable bandera vacía. Abro el archivo en modo lectura. Intento leer el archivo usando el código proporcionado por el usuario. Luego cierro el archivo. Si la lectura fue exitosa la variable bandera permanecerá vacía que es lo que pregunto a continuación, esto significa que ese código ya existe, por lo que se lo informo al usuario y vuelvo a pedirle un código. Si no existe ningún registro con el código que el usuario ingresó (cláusula INVALID KEY del READ), entonces muevo "I" a la bandera haciéndo falsa la condición del IF que sigue, por lo que el flujo del programa procederá a pedir los datos que faltan antes de escribir el registro. Notar que para escribir en el registro abro el archivo en modo I-O (lectura-escritura).

No hay un equivalente del NEXT/PREVIUOS del READ para WRITE. Pero una variaciónde esta sentencia puede ser usada en archivos con organización SEQUENTIAL, la cual es:

Sintaxis de WRITE para SEQUENTIALs
WRITE nombre-lógico-del-archivo FROM identificador.

La cláusula FROM indica que previo a la escritura, se debe mover el valor contenido en indentificador al registro lógico del archivo. Esta forma de la sentencia WRITE es útil para usarse en archivos de texto o para hacer salidas de impresora.

REWRITE.

Con esta sentencia podemos reescribir un registro ya existente. Le caben las mismas restricciones que a READ en modo de acceso RANDOM, es decir, hay que proveer una clave primaria y ésta debe existir en el archivo, o lo que es lo mismo, el registro a reescribir debe existir ya en el archivo. Y de la misma manera no puede reescribirse un registro que no existe en el archivo. La sentencia, otra vez es similar a la de READ para archivos en modo de acceso RANDOM:

Sintaxis de REWRITE.
REWRITE nombre-lógico-del-archivo [[NOT] INVALID KEY sentencia].

Si se ha especificado la cláusula INVALID KEY esta se usará cuando el valor contenido en el campo de índice primario del registro que se desea reemplazar no existe en el archivo. O bien, cuando el valor del campo que referencia cualquiera de los índices secundarios ya existe en el archivo y no se especificó WITH DUPLICATES.

Esta sentencia se puede usar con todos los modos de accesos siempre que el archivo no haya sido abierto en modo INPUT.

DELETE.

Finalmente tenemos la sentencia que elimina un registro del archivo la cual se puede usar en un archivo abierto en modo I-O o OUTPUT. La sintaxis es similar a READ y le caben también las mismas consideraciones que aquella.

Sintaxis de DELETE.
DELETE nombre-lógico-del-archivo [[NOT] INVALID KEY sentencia].

DELETE se usa para otra cosa además de borrar un registro. Sirve también para borrar físicamente un archivo, cualquier archivo; la sintaxis para esto es:

Sintaxis de DELETE para borrar archivos.
DELETE FILE nombre-lógico-del-archivo.

Archivos de líneas.

Los archivos que se declaran con organización SEQUENTIAL no tienen modo de acceso, al menos no es necesario especificarlo ya que todos son equivalentes al modo de acceso sequencial. No tienen índices tampoco por lo que no es necesario especificar esta cláusula en la sentencia SELECT.

Sintaxis de SELECT para archivos secuenciales.
SELECT [OPTIONAL] nombre-lógico-del-archivo ASSIGN TO nombre-físico-del-archivo

ORGANIZATION [LINE/BINARY] SEQUENTIAL.

La cláusula opcional OPTIONAL es igual a la ya explicada. Mientras que las cláusulas opcionales LINE y BINARY merecen una explicación aparte.

El registro de un archivo secuencial tampoco necesita ser complicado, basta con declarar cualquier estructura teniendo en cuenta que el largo total de ese registro será lo que se lea del archivo tal cual está en el archivo físico. De modo que si tenemos una declaración de registro tal que tiene un solo campo de tipo PIC X(80) se leerán 80 bytes interpretándo se cada uno como caracteres ASCII.

Cuando se declara la cláusula LINE entonces la lectura se realiza hasta el primer salto de línea o retorno de carro (caracteres ASCII 10 y 13 respectivamente), esto es ideal para leer archivos de texto. Con la salvedad de que si el registro declarado para la lectura es menor a cualquiera de las líneas leídas, la línea leída se truncará al tamaño del registro.

Por último si se utiliza la cláusula BINARY entonces se asume que los dos primeros bytes del registro leído especifica el largo total del registro (esos dos primeros bytes no son trasladados al registro lógico).

Conclusión.

Espero haber cumplido mi propósito inicial de enseñar lo básico para usar archivos de datos en COBOL.

Referencias.

  • RM/COBOL 85 User Guide - Liant Software.
  • Curso de programación RM/COBOL 85 - Fco. Javier Ceballos - 1997 - ISBN 970-15-0334-1

Por Diego Romero,