¿Qué es el BIND?

El bind es un proceso por el que debe pasar todo programa COBOL z/OS que contenga instrucciones SQL. Se trata de una particularidad del mundo mainframe que llama la atención de muchos novatos, que se hacen la siguiente pregunta: ¿por qué es necesario este proceso y no se genera el ejecutable DB2 directamente en la compilación como en otras plataformas?

Para responder a esta pregunta debemos remontarnos a los inicios del DB2 para MVS (la base del sistema operativo que posteriormente evolucionaría a 0S/390 y z/OS). En aquellos tiempos era habitual que hubiera máquinas separadas para desarrollo y producción. La de desarrollo solía tener licencia de COBOL, pero no de DB2; y la de producción solo licencia de DB2. Por ello hubo que desarrollar un sistema que permitiera hacer las compilaciones de COBOL y DB2 de forma separada.

Este peculiar proceso de compilación de los programas COBOL con DB2 en diferentes fases se mantiene actualmente y vamos a describirlo a continuación.

PRECOMPILACION

En primer lugar, dentro del JCL de compilación se ejecuta un proceso denominado precompilación, que realiza las siguientes acciones:

  1. Una verificación sintáctica sencilla de las sentencias SQL utilizando las DCLGEN incluidas en el programa. La verificación consiste en comprobar que los nombres de campos y tablas de las sentencias están definidos en la DCLGEN.
  2. Sustituye las sentencias SQL del programa fuente por llamadas al módulo de interfase con DB2, que sí pueden ser traducidas por el compilador.
  3. Mueve las sentencias SQL extraídas del programa al DBRM: un miembro con en nombre del programa que se crea en la librería DBRMLIB propia del subsistema DB2 que estemos utilizando.

COMPILACION

Una vez extraídas las sentencias DB2, el programa COBOL puede pasar por la compilación y link-edición, que generarán el código ejecutable en la librería correspondiente. Este ejecutable lleva un timestamp asociado que es idéntico al del DBRM generado en la precompilación. Dicho timestamp es muy importante para relacionar un programa COBOL con su parte DB2 en tiempo de ejecución; volveremos más adelante sobre este punto.

BIND

Después de la precompilación y la compilación, para que nuestro programa pueda acceder a DB2 debemos ejecutar el proceso de bind. El resultado del proceso dependerá del tipo de bind que ejecutemos y las opciones son dos: BIND PLAN y BIND PACKAGE (paquete). Las diferencias entre los dos tipos son bastante profundas, así que los trataremos por separado.

BIND PACKAGE

Es el tipo de bind que se utiliza para generar los paquetes (packages) que contienen el código ejecutable de un programa con DB2. Los paquetes pueden agruparse en colecciones (collections). Dentro del proceso de bind se realizan las siguientes acciones:

  • Comprobar que nuestro usuario tiene permisos tanto para realizar el bind como para ejecutar las sentencias SQL del DBRM.
  • Una verificación sintáctica completa de las sentencias SQL contra el catálogo de DB2.
  • La generación  del código ejecutable correspondiente a las sentencias SQL del DBRM. Para ello se analizan todas las sentencias y para cada una de ellas se elige el camino de acceso menos costoso en términos de uso de CPU y número de operaciones de entrada/salida.

La salida del proceso es un paquete, que puede estar contenido en una colección o no.

BIND PLAN

Tipo de bind que se emplea con los programas batch que se ejecutan directamente en un JCL y los programas online asociados a una transacción. Dichos programas pueden tener sentencias SQL o no (lo más habitual actualmente es que no las tengan).

Como párametros de entrada al proceso tendremos que indicar las ubicaciones en las que el sistema debe buscar los ejecutables DB2 de toda la unidad de ejecución (programa principal y subprogramas). Para ello debemos indicar los nombres de colecciones y/o paquetes involucrados. Es requisito previo haber realizado el BIND PACKAGE de los DBRM en cuestión.

La salida del proceso es un plan: objeto que contiene información sobre las dependencias DB2 del programa.

Nota: antes de la introducción de los paquetes, los BIND PLAN eran diferentes; dentro del plan se incluía todo el código ejecutable DB2, tanto de los DBRM del programa principal como de los subprogramas llamados. Por ejemplo, para un programa A con DB2 que llamara a un programa B con DB2, que a su vez llamara a otro programa C con DB2, la entrada al bind eran los DBRM de los tres programas. Este es ejemplo sencillo, pero en casos de cadenas de llamadas complejas con cientos de subprogramas, la modificación de uno solo de ellos obligaba a hacer de nuevo el BIND PLAN de todos los DBRM. Este problema se solucionó con la llegada de los paquetes.

TIEMPO DE EJECUCIÓN

En los apartados anteriores hemos visto que como resultado de todo el proceso tenemos tres elementos: el ejecutable del programa, el plan y los paquetes referenciados por el plan. A continuación vamos a ver cómo interviene cada uno de ellos en tiempo de ejecución y cómo se utiliza el timestamp que mencionamos en el apartado de la compilación. Para ello usaremos un ejemplo.

Supongamos que se va a ejecutar un programa batch PGM1 (sin DB2), que llama a PGM2 (con DB2) y PGM3 (con DB2).

Previamente tendremos que haber hecho:

  • Precompilación y compilación de PGM1, PGM2 y PGM3.
  • BIND PACKAGE de PGM2 incluyéndolo en la colección PGMCONTA.
  • BIND PACKAGE de PGM3 incluyéndolo en la colección PGMLISTAD.
  • BIND PLAN de PGM1 incluyendo las colecciones PGMCONTA y PGMLISTAD.

En tiempo de ejecución, DB2:

  • Buscará un paquete PGM2 en PGMCONTA con el mismo timestamp que la colección del programa y lo encontrará.
  • Buscará un paquete PGM3 en PGMCONTA con el mismo timestamp que la colección del programa y no lo encontrará; continuará la búsqueda en PGMLISTAD y sí lo encontrará.

En este ejemplo todo iría bien y las sentencias DB2 podrían ejecutarse. Desgraciadamente no siempre ocurre así y es muy habitual encontrarnos con un SQLCODE -805 al ejecutar un programa con DB2. Este error se produce cuando DB2 no consigue encontrar alguno de los paquetes.

Volviendo al ejemplo anterior, obtendríamos un SQLCODE -805 en este caso:

Compilamos PGM2, pero no hacemos el BIND PACKAGE; la compilación ha generado un nuevo timestamp. El paquete PGM2 sigue existiendo en la colección PGMCONTA, pero con un timestamp diferente. Por tanto, al ejecutar el programa la búsqueda del paquete fallará con un -805.

Contadores y totales por campo clave en un Sort

Cuando en un Sort queremos obtener totales o contadores por un campo clave, lo más sencillo es utilizar los parámetros de informe de la instrucción OUTFIL. Los analizamos en un artículo anterior, pero aquí solo vamos a usar los que necesitamos para nuestro propósito. Los contadores o totales pueden ir en un fichero aparte o bien junto con los registros de detalle, a continuación del último registro de cada valor del campo clave. Lógicamente hay que ordenar el fichero por el campo clave.

Por ejemplo, supongamos que tenemos un fichero de entrada (DD SORTIN) con la provincia en las posiciones de la 5 a la 19 y queremos obtener un fichero con el número de registros de cada provincia. La SYSIN a incluir en el Sort será la siguiente:

  
SORT FIELDS=(5,15,CH,A)             - Ordenación                           
OUTFIL FNAMES=SALIDA,               - DD de salida                        
REMOVECC,                           - Sin caracteres ANSI de impresión
NODETAIL,                           - Sin registros de detalle                
SECTIONS=(5,15,                     - Campo de ruptura                          
TRAILER3=('Provincia: '               
          ,5,15,                     
          ' Total registros: ',     
          COUNT=(EDIT=(III.IIT))))  - Contador y máscara de edición 

Para que el fichero de salida lleve no solo los contadores sino también los registros del fichero de entrada, basta con eliminar la instrucción NODETAIL.

Cuando se quiere obtener la suma de un campo numérico por campo clave, hay que cambiar el operador COUNT por TOTAL e indicar la posición de inicio y la longitud del campo a sumar. Por ejemplo, supongamos que el fichero del ejemplo anterior contiene un campo con el importe de las ventas en la posición 30 (numérico empaquetado de 5 posiciones). Con la siguiente SYSIN generaríamos el fichero de sumas por provincia:


SORT FIELDS=(5,15,CH,A)                      - Campo clave                           
OUTFIL FNAMES=SALIDA,                        - DD de salida                        
REMOVECC,                                    - Sin caracteres ANSI de impresión
NODETAIL,                                    - Sin registros de detalle                
SECTIONS=(5,15,                              - Campo de ruptura                          
TRAILER3=('Provincia: '              
          ,5,15,                     
          ' Total ventas: ',         
          TOTAL=(30,5,PD,EDIT=(IIIIIT,TT))   - Total y máscara 



Fecha del sistema en un JCL

Para incluir la fecha de sistema en un Sort, ya sea al construir los registros (INREC, OUTREC, BUILD) o en una condición (INCLUDE, OMIT, IFTHEN, etc.), tenemos disponibles las siguientes constantes:

Constante Formato
DATE1  C’yyyymmdd’
DATE1(c) C’yyyycmmcdd’
DATE1P P’yyyymmdd’
DATE2 C’yyyymm’
DATE2(c) C’yyyycmm’
DATE2P P’yyyymm’
DATE3 C’yyyyddd’
DATE3(c) C’yyyycddd’
DATE3P P’yyyyddd’
DATE4 C’yyyy-mm-dd-hh.mm.ss’
DATE5 C’yyyy-mm-dd-hh.mm.ss.nnnnnn’

Donde ‘yyyyy’ es el año; ‘mm’ el mes; ‘dd’ el día del mes‘; ‘ddd’ el día del año;  c’ indica un carácter separador; ‘C’ formato numérico display;  y ‘P’ formato numérico empaquetado.

Ejemplos
Suponiendo que ejecutemos el 3 de septiembre de 2019:

INCLUDE COND=(5,8,LE,DATE1)
Filtra el fichero de entrada para incluir solo los registros cuya fecha situada en la posición 5 sea menor o igual que 20190903.

BUILD=(DATE5,10,3,20,4)
Incluye la fecha en formato timestamp de 26 posiciones (2019-09-03-10.00.15.122455) al inicio de los registros del fichero de salida.

OUTREC=(1,10,(DATE2(/))
Coloca en la posición 11 del registro de salida el año y el mes separados por una barra (2019/03).

BUILD=(1,20,DATE3(-))
Incluye en la posición 21 el año y el día del año separados por un guión (2019-246).

Joins in DB2 SQL

An SQL join is a query that combines data from two tables using one or more common columns to create a relationship between both tables.

The four main join types produce the following results (T1 is the first table and T2 the second):

INNER JOIN: Rows from T1 and T2 that satisfy the join condition (matched).

LEFT OUTER JOIN: Rows from T1 and T2 which statisfy the join condition (matched) and rows from T1 that do not satisfy the join condition matched with a null row from T2.

RIGHT OUTER JOIN: Rows from T1 and T2 which statisfy the join condition (matched) and rows from T2 that do not satisfy the join condition matched with a null row from T1.

FULL OUTER JOIN: Rows from T1 and T2 which statisfy the join condition (matched); rows from T1 that do not satisfy the join condition matched with a null row from T2; and rows from T2 which do not satisfy the join condition matched with a null row from T1.

Examples

Given the following two tables:

COMPANIES

COMP_CODE COMPANY
1 S.F. SOFTWARE
2 GARCIA FRUITS
3 SMITH ALUMINIUM
5 M.S. IMPRESSIONS

EMPLOYEES

EMPL_CODE COMP_CODE EMPLOYEE
1 2 JOHN SMITH
2 2 MARY JONES
3 4 RICHARD ROSSI
4 5 STEVE RAMOS

INNER JOIN

SELECT EMPLOYEE, COMPANY
FROM EMPLOYES INNER JOIN COMPANIES
ON EMPLOYEES.EMPL_CODE = COMPANIES.COMP_CODE

or simply

SELECT EMPLOYEE, COMPANY
FROM EMPLOYES, COMPANIES
WHERE EMPLOYEES.EMPL_CODE = COMPANIES.COMP_CODE

We get the employee’s names and the company in which they work. If the company code of an employee doesn´t exist in the COMPANIES table, no row will be produced for that employee in the result set.

EMPLOYEE COMPANY
MARY JONES GARCIA FRUITS
STEVE RAMOS M. S. IMPRESSIONS
JOHN SMITH GARCIA FRUITS


LEFT JOIN

SELECT EMPLOYEE, COMPANY
FROM EMPLOYEES LEFT JOIN COMPANIES
ON COMPANIES.COMP_CODE = EMPLOYEES.EMPL_CODE

We obtain the employee’s names and the company in which they work. Employees whose company doesn´t exist in the COMPANIES table also appear in the result set, but with a null value in the column belonging to the COMPANIES table.

EMPLOYEE COMPANY
MARY JONES GARCIA FRUITS
RICHARD ROSSI (null)
STEVE RAMOS M. S IMPRESSIONS
JOHN SMITH GARCIA FRUITS

RIGHT JOIN

SELECT EMPLOYEE, COMPANY
FROM EMPLOYESS RIGHT JOIN COMPANIES
ON COMPANIESS.COMP_CODE = EMPLOYESS.EMPL_CODE

We obtain the employee’s names and the company in which they work. Companies without workers in the EMLOYEES table also appear in the result set, but with a null value in the column belonging to the EMPLOYEES table.

EMPLOYEE COMPANY
(null) S. F. SOFTWARE
JOHN SMITH GARCIA FRUITS
MARY JONES GARCIA FRUITS
(null) SMITH ALUMINIUM
STEVE RAMOS M.S. IMPRESSIONS

FULL OUTER JOIN

SELECT EMPLOYEE, COMPANY
FROM EMPLOYEES FULL OUTER JOIN COMPANIES
ON COMPANIES.COMP_CODE = EMPLOYEES.EMP_CODE

We obtain the employee’s names and the company in which they work. Companies without workers in the EMPLOYEES table and employees whose company doesn´t exist in the COMPANIES table also appear in the result set, but with corresponding null values.

EMPLOYEE COMPANY
MARY JONES GARCIA FRUITS
RICHARD ROSSI (null)
STEVE RAMOS M.S. IMPRESSIONS
JOHN SMITH GARCIA FRUITS
(null) S. F. SOFTWARE
(null) SMITH ALUMINIUM



Contar caracteres de una variable alfanumérica en COBOL

En el COBOL de z/OS, la función intrínseca LENGTH OF devuelve el número de bytes que ocupa una variable. Por lo tanto, utilizada con una variable alfanumérica, devolverá el total de bytes que ocupa la variable, aunque todos ellos contengan espacios.

Sin embargo, en muchos casos nos interesa conocer el número de carácteres que contiene una variable alfanumérica sin contar los espacios del final. Para ello podemos usar la instrucción INSPECT, que permite contar el número de espacios al inicio, combinada con la función REVERSE, que invierte el contenido de una variable alfanumérica.

Siendo W-LONG y W-CONT variables numéricas y W-CADENA la variable alfanumérica que queremos examinar, el siguiente código dejará en W-LONG el número de caracteres contenido en la variable:

MOVE ZERO TO W-CONT

INSPECT FUNCTION REVERSE(W-CADENA)
TALLYING W-CONT FOR LEADING SPACES

COMPUTE W-LONG = LENGTH OF W-CADENA - W-CONT

Preguntas y respuestas sobre JOINKEYS

1 – ¿Hay que ordenar previamente los ficheros?

No, ya que JOINKEYS los ordena automáticamente.

De todas formas, si sabemos que los ficheros ya están ordenados podemos usar el operador SORTED para reducir el tiempo de ejecución del paso. Con este operador se evita la ordenación, aunque JOINKEYS comprueba que los ficheros estén realmente ordenados y termina con el siguiente error si detecta que no lo están:

ICE424A ddname (Fn) KEY IS OUT OF SEQUENCE

Se puede utilizar el operador NOSEQCK para evitar esta comprobación y reducir aún más el tiempo de ejecución; pero si los registros no están ordenados, los resultados serán imprevisibles, ya que no estaremos haciendo realmente un cruce.

Veamos un ejemplo de sentencias de control para cada una de las opciones.

Ordenación automática:
JOINKEYS FILE=F1,FIELDS=(1,3,A)
JOINKEYS FILE=F2,FIELDS=(12,3,A)

Sin ordenación automática, con test de ordenación:
JOINKEYS FILE=F1,FIELDS=(1,3,A),SORTED
JOINKEYS FILE=F2,FIELDS=(12,3,A),SORTED

Sin ordenación automática, sin test de ordenación:
JOINKEYS FILE=F1,FIELDS=(1,3,A),SORTED,NOSEQCK
JOINKEYS FILE=F2,FIELDS=(12,3,A),SORTED,NOSEQCK

2 – ¿Es obligatorio incluir la sentencia JOIN UNPAIRED?

No, solo es necesaria si queremos que el fichero resultado del cruce contenga también los registros que no cruzan del fichero 1, del 2 o de ambos. Si solo necesitamos los registros que cruzan, podemos usar la forma sencilla de JOINKEYS, como en el siguiente ejemplo:


//CRUCE EXEC PGM=SORT                                    
//SYSOUT DD SYSOUT=*                                         
//SORTJNF1 DD DSN=FICH.ENTRADA1,DISP=SHR           
//SORTJNF2 DD DSN=FICH.ENTRADA2.,DISP=SHR           
//SORTOUT DD DSN=FICH.SALIDA.SMSSTD, 
//           DISP=(,CATLG,DELETE),SPACE=(TRK,(1,1),RLSE),
//           DCB=(RECFM=FB,LRECL=65,BLKSIZE=0)
//SYSIN DD *                                                 
  JOINKEYS FILE=F1,FIELDS=(1,3,A)                             
  JOINKEYS FILE=F2,FIELDS=(10,3,A)                            
  REFORMAT FIELDS=(F1:1,10,F2:13,7)                            
  SORT FIELDS=COPY     

3 – Con la instrucción FIELDS se especifican las posiciones de los campos de cruce y el tipo de ordenación (ascendente o descendente), pero no el formato. ¿Qué ocurre si el formato de los campos no es igual en los dos ficheros?

Los campos de cruce se tratan como binarios. Si las dos claves de cruce tienen formatos con valores binarios diferentes, habrá que convertir una las claves para poder cruzar correctamente. Esta conversión la podemos hacer con un SORT previo, o mejor mediante las fichas JNF1CNTL y JNF2CNTL del propio JOINKEYS.

En el siguiente ejemplo podemos ver el tipo de conversión de claves más común: de numérico empaquetado a numérico normal. Tenemos los siguientes ficheros:

Fichero 1: clave en las posiciones 9 a 13 (ZD – numérico no empaquetado).

Vista normal
=COLS> ----+----1----+----2----+----3----+         
000001 2018120333333PERICO PALOTES                 
000002 2018111222222JUAN PALOMEQUE                 
000003 2018060311111BENEDICTO BARAHONA             

Vista hexadecimal (se activa con el comando HEX y se desactiva con HEX OFF)
=COLS>  ----+----1----+----2----+----3----+         
 000001 2018120333333PERICO PALOTES                 
        FFFFFFFFFFFFFDCDCCD4DCDDECE44444444         
        20181203333337599360713635200000000         
 ---------------------------------------------------
 000002 2018111222222JUAN PALOMEQUE                 
        FFFFFFFFFFFFFDECD4DCDDDCDEC44444444         
        20181112222221415071364584500000000         
 ---------------------------------------------------
 000003 2018060311111BENEDICTO BARAHONA             
        FFFFFFFFFFFFFCCDCCCCED4CCDCCDDC4444         
        20180603111112555493360219186510000         
 ---------------------------------------------------

Fichero 2: clave en las posiciones 9 a 11 (PD – numérico empaquetado)


Vista normal (la clave no es visualizable al ser un empaquetado)
=COLS> ----+----1----+----2----+----3---          
000001            C/ LIMÓN, 21                    
000002            AVDA. EL TORITO, 56             
000003            PLAZA DE ESPAÑA, 1              

Vista hexadecimal
=COLS>  ----+----1----+----2----+----3---          
 000001 C1234567   C/ LIMÓN, 21                    
        CFFFFFFF111C64DCDED64FF4444444444          
        3123456711C310394E5B0210000000000          
 --------------------------------------------------
 000002 C1234567   AVDA. EL TORITO, 56             
        CFFFFFFF222CECC44CD4EDDCED64FF444          
        3123456722C1541B0530369936B056000          
 --------------------------------------------------
 000003 C1234567   PLAZA DE ESPAÑA, 1              
        CFFFFFFF333DDCEC4CC4CEDC7C64F4444          
        3123456733C7319104505271B1B010000          
 --------------------------------------------------

Paso de JOINKEYS en el que indicamos que se debe convertir la clave del fichero 2 mediante un INREC en la ficha JNF2CNTL. Los dos ficheros entrarán en el proceso de cruce con el mismo formato de clave. Hay que tener en cuenta que la longitud del fichero 2 convertido puede cambiar En este ejemplo aumenta dos bytes, por lo que los campos del fichero están desplazados dos posiciones en el REFORMAT si tomamos como referencia las posiciones del fichero de entrada original.


//CRUCE  EXEC PGM=SORT                                    
 //SYSOUT DD SYSOUT=*                                      
 //SORTJNF1 DD DSN=USUARIO.ENTRADA1.SMSMES,DISP=SHR        
 //SORTJNF2 DD DSN=USUARIO.ENTRADA2.SMSMES,DISP=SHR        
 //SORTOUT  DD DSN=USUARIO.SALIDA1.SMSMES,                 
 //            DISP=(,CATLG,DELETE),SPACE=(TRK,(1,1),RLSE),
 //            DCB=(RECFM=FB,LRECL=49,BLKSIZE=0)           
 //JNF2CNTL  DD *                                          
   INREC FIELDS=(1,8,9,3,PD,TO=ZD,LENGTH=5,12,22)          
 //SYSIN DD *                                              
   JOINKEYS FILE=F1,FIELDS=(9,5,A)                         
   JOINKEYS FILE=F2,FIELDS=(9,5,A)                         
   REFORMAT FIELDS=(F1:9,27,F2:14,21)                      
   OPTION COPY                                             

Numerar registros y añadir contador en un Sort

Numerar registros

Al procesar un fichero, muchas veces surge la necesidad de numerar los registros. Es decir, incluir un en cada registro un número secuencial que identifique la posición del mismo en el fichero. Con DFSORT podemos hacerlo incluyendo el operador SEQNUM al construir un registro mediante BUILD/INREC/OUTREC. SEQNUM permite indicar la longitud y el formato deseados para el contador.

Por ejemplo, podríamos incluir al principio de un fichero 300 posiciones un número secuencial de 5 dígitos (no empaquetado) mediante el siguiente OUTREC:

OUTREC BUILD=(SEQNUM,5,ZD,1,300)

Registro de cola con contador de registros

Otra necesidad habitual es incluir el número total de registros del fichero en un registro de cola, algo que podemos hacer mediante los operadores de informe TRAILER1 y COUNT, que pertenecen al grupo de operadores de informe de DFSORT y se incluyen siempre dentro de un OUTFIL.

Por ejemplo, para incluir un registro de cola con el total de registros un fichero, con 5 dígitos y formato numérico no empaquetado:

OUTFIL FNAMES=SALIDA,
TRAILER1=('Número de registros: ', COUNT=(TO=ZD,LENGTH=5)),
REMOVECC

Incluimos el operador REMOVECC para que no se añadan al fichero los caracteres de control de carro que se incluyen por defecto al usar los operadores de informe.


Niveles de aislamiento en DB2

Cuando varias transacciones DB2 se ejecutan simultáneamente, pueden darse los problemas que se detallan a continuación:

  • Actualizaciones perdidas: se producen cuando dos transacciones leen los mismos datos e intentan actualizarlos a la vez, lo que provoca que una de los dos actualizaciones se pierda. Por ejemplo: las transacciones A y B leen la misma fila y calculan nuevos valores para la misma basados en los originales. Si la transacción 1 actualiza la fila con su nuevo valor e inmediatamente después la transacción 2 hace lo mismo con el suyo, se perderá la actualización de la transacción 1
  • Lecturas sucias: fenómeno que se da cuando una transacción lee una fila que acaba de ser actualizada pero que se encuentra a la espera de commit. Por ejemplo: la transacción 1 modifica una fila y la transacción 2 lee dicha fila antes de que se haga commit. Si la transacción 1 hace rollback del cambio, la transacción 2 habrá leído datos que nunca existieron realmente.
  • Lecturas no repetibles: se producen si una transacción lee la misma fila dos veces y obtiene resultados diferentes cada vez. Por ejemplo: la transacción 1 lee una fila y a continuación la transacción 2 modifica o borra dicha fila. Si la transacción 1 intenta leer de nuevo la fila, recuperará valores diferentes (si se actualizó la fila) o se encontrará con que no existe (si se borró).
  • Lecturas fantasma: ocurren cuando una fila cumple determinado criterio de selección, pero no aparece inicialmente en las consultas. Por ejemplo: la transacción 1 recupera un conjunto de filas que cumplen un criterio de selección. Después la transacción 2 inserta una fila que también cumple el criterio de la transacción 1. Si la transacción 1 vuelve a ejecutar la consulta original, obtendrá un conjunto de filas diferente (se habrá añadido la fila insertada por la transacción 2).

Para evitar estos problemas de concurrencia, DB2 ofrece los niveles de aislamiento (isolation levels), que se definen al hacer el bind del programa (opción ISOLATION [RR | RS | CS | UR] del comando BIND). Son los siguientes:

  • Repeatable Read (RR – Lectura repetible )
  • Read Stability (RS – Estabilidad de lectura)
  • Cursor Stability (CS – Estabilidad de cursor )
  • Uncommitted Read (UR – Lectura no confirmada)

En la siguiente tabla vemos los fenómenos que se pueden producir con cada nivel de aislamiento.

Nivel de aislamiento

Fenómenos

Act. Perdidas Lecturas sucias Lect. no repetibles Lect. fantasma
Repeatable Read No No No No
Read Stability No No No
Cursor Stability No No
Uncommited Read No

Como se puede ver en la tabla, el problema de las actualizaciones perdidas queda eliminado bajo cualquier nivel de aislamiento , ya que el gestor de base datos coloca un bloqueo exclusivo sobre cada fila insertada, actualizada o eliminada. Por lo tanto, todos los niveles de aislamiento garantizan que cualquier fila modificada por una transacción no será modificada por otra transacción mientras dure la ejecución de la transacción original.
A continuación se detallan las características de los diferentes niveles de aislamiento.

Repeatable Read (RR – Lectura repetible)
El nivel de aislamiento Repeatable Read es el más restrictivo. Cuando se aplica a una transacción, los efectos de la misma están completamente aislados de los efectos del resto de transacciones que se ejecuten a la vez. Esto implica que no puedan producirse actualizaciones perdidas, lecturas no repetibles, lecturas sucias ni lecturas fantasma.

Con el nivel de aislamiento Repeatable Read, todas las filas a las que accede la transacción quedan bloqueadas durante el periodo de ejecución de la transacción. Por tanto, si se ejecuta la misma SELECT varias veces dentro de la transacción, el conjunto de filas obtenido será siempre el mismo y la transacción podrá realizar cualquier operación sobre ellas. Las transacciones que se ejecutan bajo este nivel de aislamiento pueden recuperar el mismo conjunto de filas un número ilimitado de veces y realizar cualquier operación sobre las mismas. El resto de transacciones tienen prohibido realizar operaciones de insert, update o delete que puedan modificar las filas a las que accede la transacción propietaria (con nivel de aislamiento Repeatable Read) mientras esta esté activa.

Para garantizar que una transacción con este nivel de aislamiento no se vea afectada por otras transacciones, se bloquean todas las filas referenciadas (no solo las recuperadas o modificadas). Por tanto, si la transacción escanea 1.000 filas para recuperar 10, el bloqueo se adquiere y mantiene sobre las 1.000 filas escaneadas.

Read Stability (RS – Estabilidad de lectura)
El nivel de aislamiento Read Stability no es tan restrictivo como Repeatable Read. Por ello, no aísla totalmente a la transacción del resto de transacciones que se ejecutan simultáneamente. Cuando se utiliza este nivel de aislamiento, no pueden darse actualizaciones perdidas, lecturas sucias ni lecturas no repetibles; pero sí pueden darse lecturas fantasma. Esto es debido a que con Read Stability solo se bloquean las filas recuperadas o modificadas por la transacción que se aisla. Si una transacción escanea 1.000 filas para recuperar 10, solo se adquieren y mantienen bloqueos sobre las 10 filas recuperadas. El hecho de que se adquieran menos bloqueos permite que más transacciones puedan ejecutarse simultáneamente; pero si la transacción aislada ejecuta la misma consulta varias veces, pueden obtenerse resultados diferentes cada vez.

Igual que con el nivel de aislamiento Repeatable Read, las transacciones que se ejecutan bajo Read Stability, pueden recuperar un conjunto de filas y realizar cualquier operación sobre ellas. El resto de transacciones en ejecución tienen prohibido realizar operaciones de actualización y borrado que puedan afectar al conjunto de filas recuperadas por la transacción que se aísla (mientras la transacción permanece activa).

Como el resto de transacciones pueden insertar en cualquier tabla o vista actualizable de la base de datos, es posible que se inserten filas que cumplan el criterio de selección de la transacción aislada. Dichas filas aparecerán como lecturas fantasma en los conjuntos de filas recuperados por consultas posteriores.

Cursor Stability (CS – Estabilidad de cursor)
El nivel de aislamiento Cursor Stability es aún menos restrictivo que Read Stability. Cuando se utiliza este nivel de aislamiento, no se pueden producir actualizaciones perdidas ni lecturas sucias, pero sí lecturas no repetibles y lecturas fantasma.

Con este nivel de aislamiento solo se bloquea la fila a la que apunta el cursor actualizable que se utiliza para recuperar los datos. La duración del bloqueo dependerá de si se ha modificado la fila o no: si se ha modificado, el bloqueo se mantiene hasta que se hace commit. Si no se ha modificado, se libera al recuperar la siguiente fila del cursor o cuando termina la transacción.

Cursor Stability es el nivel de aislamiento por defecto.

Uncommitted read (UR – Lectura no confirmada)
El nivel de aislamiento Uncommited Read es el menos restrictivo de todos, ya que generalmente no aísla la transacción de otras transacciones que se ejecuten simultáneamente. Con este nivel no pueden producirse actualizaciones perdidas, pero sí el resto de fenómenos: lecturas sucias, lecturas no repetibles y lecturas fantasma.

El funcionamiento de UR depende del tipo de cursor que se utilice

  • Si el cursor es de solo lectura, las transacciones que se ejecutan bajo este nivel de aislamiento pueden acceder a cambios en datos realizados por otras transacciones antes de que se haga commit de los mismos. Sin embargo, no ocurre lo mismo cuando otras acciones hacen altas y o bajas de objetos de base de datos (tablas, índices y vistas). En estos casos, la transacción que crea o elimina el/los objeto/s debe hacer commit antes de que la transacción con nivel de aislamiento UR pueda verlos.
  • Si el cursor es actualizable, el comportamiento es exactamente igual que bajo el nivel de aislamiento CS.

Se utiliza habitualmente cuando la transacción accede a tablas o vistas de solo lectura, o cuando no supone un inconveniente acceder a datos modificados pero no no confirmados (commit).

Fuentes:
https://www.ibm.com/developerworks/data/tutorials/db2-cert6106/index.html
https://www.ibm.com/support/knowledgecenter/en/SSEPGG_11.1.0/com.ibm.db2.luw.admin.perf.doc/doc/c0004121.html



JCL: Crear fichero de entrada/salida cuando no exista

En un artículo anterior veíamos la forma de evitar el fallo de un JCL por la inexistencia de un fichero. Se trataba de crear un paso IDCAMS LISTCAT para generar un retorno 0 en caso de encontrar el fichero y 4 en caso contrario. Esto nos permitía condicionar el resto de pasos al código de retorno obtenido.

Otras veces lo que nos interesa no es evitar la ejecución del paso sino crear el fichero cuando no exista. Por ejemplo, cuando ejecutamos un programa que actualiza DB2 y usamos un fichero de control de entrada/salida para controlar los registros que se han actualizado con COMMIT y poder rearrancar desde el último tratado en caso de error. Dicho fichero no existirá en la primera ejecución, por lo que habrá que crearlo. Para ello lo definiremos con DISP=(MOD,CATLG,CATLG).

Ejemplo de ficha:

//FICHER1 DD DSN=USUARIO.NOMJCL.%%$ODATE.CONTROL
// DISP=(MOD,CATLG,CATLG),
// UNIT=SYSDA,SPACE=(TRK,(10,5),RLSE),
// DCB=(RECFM=FB,LRECL=11,BLKSIZE=0,DSORG=PS)

Este fichero se puede utilizar en un programa COBOL de las siguientes formas dependiendo del tipo de apertura:

    • OPEN INPUT: Lectura a partir del primer registro.
    • OPEN EXTEND: Escritura a partir del último registro.
    • OPEN OUTPUT: Escritura borrando previamente el contenido del fichero.



Tipos de join en DB2

Un join en SQL es una consulta en la que se combinan dos tablas relacionadas mediante una cláusula WHERE en la que intervienen uno o más campos comunes entre ambas.

El resultado depende del tipo de join elegido y puede incluir filas nulas si la condición no se cumple para alguna de las tablas.

Siendo T1 la primera tabla y T2 la segunda, los resultados obtenidos con cada tipo de join son los siguientes.

INNER JOIN:  Filas de T1 y T2 que cumplen la condición emparejadas.

LEFT OUTER JOIN:  Filas de T1 y T2 que cumplen la condición emparejadas y, por cada fila de T1 que no cumple la condición, dicha fila concatenada con una fila nula de T2.

RIGHT OUTER JOIN: Filas de T1 y T2 que cumplen la condición emparejadas y, por cada fila de T2 que no cumple la condición, dicha fila concatenada con una fila nula de T1.

FULL OUTER JOIN: Filas de T1 y T2 que cumplen la condición emparejadas y, por cada fila de T1 que no cumple la condición, dicha fila concatenada con una fila nula de T2; y, por cada fila de T2 que no cumple la condición, dicha fila concatenada con una fila nula de T1.

Ejemplos

Dadas las siguientes tablas:

EMPRESAS

COD_EMPRESA EMPRESA
1 S.F. SOFTWARE
2 FRUTAS GARCIA
3 ALUMINIOS RUIZ
5 IMPRESIONES M.
S.

TRABAJADORES

COD_TRABAJADOR COD_EMPRESA TRABAJADOR
1 2 JUAN PEREZ
2 2 PEDRO
SÁNCHEZ
3 4 FELIPE
LÓPEZ
4 5 RODRIGO TORRES

INNER JOIN 

SELECT TRABAJADOR, EMPRESA
FROM TRABAJADORES INNER JOIN EMPRESAS
ON TRABAJADORES.COD_EMPRESA =  EMPRESAS.COD_EMPRESA

o bien simplemente:

SELECT TRABAJADOR, EMPRESA
FROM TRABAJADORES, EMPRESAS
WHERE TRABAJADORES.COD_EMPRESA = EMPRESAS.COD_EMPRESA

Se obtienen los nombres de los trabajadores y el nombre de la empresa en la que trabajan.

TRABAJADOR EMPRESA
PEDRO SANCHEZ FRUTAS GARCIA
RODRIGO TORRES IMPRESIONES M. S.
JUAN PEREZ FRUTAS GARCIA


LEFT JOIN 

SELECT NOMBRE_TRABAJADOR, NOMBRE_EMPRESA
FROM TRABAJADORES LEFT JOIN EMPRESAS
ON EMPRESAS.COD_EMPRESA = TRABAJADORES.COD_EMPRESA

Se obtienen los nombres de los trabajadores y el nombre de la empresa en que trabajan. Los trabajadores cuya empresa no existe en la tabla EMPRESAS aparecen también en los resultados, pero un con valor nulo en la columna proveniente de dicha tabla.

TRABAJADOR EMPRESA
PEDRO SANCHEZ FRUTAS GARCIA
FELIPE LOPEZ (null)
RODRIGO TORRES IMPRESIONES M. S.
JUAN PEREZ FRUTAS GARCIA

RIGHT JOIN

SELECT TRABAJADOR, EMPRESA
FROM TRABAJADORES RIGHT JOIN EMPRESAS
ON EMPRESAS.COD_EMPRESA = TRABAJADORES.COD_EMPRESA

Se obtienen los nombres de los trabajadores y el nombre de la empresa en que trabajan. Las empresas que no tengan ningún trabajador registrado en la tabla TRABAJADORES aparecen también en los resultados, pero un con valor nulo en la columna proveniente de dicha tabla.

TRABAJADOR EMPRESA
(null) S. F. SOFTWARE
JUAN PEREZ FRUTAS GARCIA
PEDRO SANCHEZ FRUTAS GARCIA
(null) ALUMINIOS RUIZ
RODRIGO TORRES IMPRESIONES M. S.

FULL OUTER JOIN

SELECT TRABAJADOR, EMPRESA
FROM TRABAJADORES FULL OUTER JOIN EMPRESAS
ON EMPRESAS.COD_EMPRESA = TRABAJADORES.COD_EMPRESA

Se obtienen los nombres de los trabajadores y el nombre de la empresa en que trabajan. También aparecen en los resultados los trabajadores que no tienen fila asociada en la tabla EMPRESAS y las empresas sin fila asociada en la tabla TRABAJADORES.

TRABAJADOR EMPRESA
PEDRO SANCHEZ FRUTAS GARCIA
FELIPE LOPEZ (null)
RODRIGO TORRES IMPRESIONES M. S.
JUAN PEREZ FRUTAS GARCIA
(null) S. F. SOFTWARE
(null) ALUMINIOS RUIZ