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

miércoles, 22 de junio de 2022

La beca COBOL (agilecobol)

 La famosa beca COBOL.

¿Qué cobolero no la ha vivido? Alguno hay, pero seguro que a muchos os suena esa famosa formación inicial de semanas o meses, antes de sumergirte en el mundo de la programación cobol.

El año pasado tuve mi primer contacto como formadora en una beca cobol. Me tocó la "parte 2" con COBOL y DB2.

Era mi primera vez y como el covid aún andaba por ahí me tocó dar toda la formación en remoto, sin más herramientas que una reunión de Teams.

Al segundo día supe que aquello no marchaba bien. En una llamada con 8 personas, los alumnos perdían el "preguntarle al compañero". Perdían el ayudarse entre ellos. Y tenían que escuchar cómo yo iba resolviendo los problemas de cada uno. Era un caos.

Pero no hay nada mejor que la necesidad. Si los que llevan tiempo en esto no hacían más que decir que lo mejor para aprender a programar era hacer Pair Programming, pues había que intentarlo.
Preparé una pequeña presentación para explicar en qué consistía y elegí las parejas (fuimos haciendo cambios de pareja a lo largo de la semana).

Et voilà! El ánimo subió, los ejercicios salían más rápido, yo seguía pasándome por sus reuniones a dos cuando me necesitaban y todo el mundo le pilló el punto en un instante.
Formación salvada.

Este año he tenido otra oportunidad de participar como formadora en una beca cobol, pero esta vez en presencial.
Qué diferencia. Echaba taaaanto de menos la pizarra y los rotuladores... xd Y ver la cara a la gente. No hay nada como verle la cara a alguien para saber si está en dificultades sin que diga nada.

En la anterior beca había aprovechado para hacer una retrospectiva sobre la formación y así detectar puntos de mejora para la siguiente. Y en ésta me apliqué el cuento. Por ejemplo "mucha teoría seguida sin ver nada de práctica al principio", pues nos hacemos un "Hola mundo" el primer día y desde ahí avanzamos.

Esta vez no hubo pair programming así que pude comparar las dos situaciones y sacar conclusiones:

1. Enseñar en presencial gana de calle a enseñar en remoto.
Poder pintar en la pizarra, poder señalar con el dedo (en la pizarra o en el proyector), poder ver la cara de la gente que no consigue que su programa compile... Podría dar 1000 razones.

2. Se puede enseñar sin Pair Programming (obvio), pero.
- Trabajando por pares consigues que todo el grupo avance a la vez (o casi xd). El que lo pilla más rápido arrastra al que lo pilla más despacio, y al revés, se frena a los "demasiado rápidos" para mantener un ritmo sostenible.
- Trabajando de manera individual, tienes al que va al doble de velocidad que el otro. Y lo tienes que entretener con otra cosa mientras los que van más despacio terminan.
- Aunque siempre se preguntan entre ellos, no aprenden tanto unos de otros.
- Los que terminan rápido se ponen a hacer "otras cosas" y dejan de estar centrados en la formación.

3. Hay que adaptar las formaciones a los tiempos que corren.
Lo que aprendí en la primera beca me sirvió para esta última. Mucha práctica, teoría después de la práctica y no antes, deja que fallen y corrige después...

¿Qué os hubiese gustado encontraros en vuestra beca COBOL? ¡Os leo en comentarios!

#thisistheway

domingo, 22 de marzo de 2020

Vuelve el Consultorio Cobol

Hola amigas y amigos del Consultorio Cobol.

Son días extraños. El estado de alarma en España nos tiene a todos en casa.
Me considero afortunada porque puedo trabajar desde casa, como muchos otros compañeros coboleros.

El teletrabajo, todos soñábamos con él. Yo ya teletrabajaba un día a la semana desde hacía más de 6 meses, pero para otros es algo completamente nuevo. Y éste es un teletrabajo permanente hasta no se sabe cuándo.
Teletrabajar tiene muchas ventajas pero también hace que cosas tan sencillas como preguntar a tu compi de al lado se vuelvan más complicadas.
Es cierto que tenemos mil herramientas para comunicarnos, compartir el escritorio, compartir archivos, trabajar sobre un mismo documento de forma colaborativa... pero no siempre es lo mismo.

Es por eso que en estos días en que todo es más difícil, ponemos a vuestra disposición todo el contenido del Consultorio Cobol una vez más.

En este tiempo hemos estado trabajando para lograr una versión mejorada del blog, y no dejaremos de hacerlo. Pero mientras dure la cuarentena creemos que, ya que no tenemos al compañero experto a un giro de silla de nosotros, al menos vamos a intentar echar una mano compartiendo los artículos del blog.

Muchos junior han empezado en pleno confinamiento y para ellos seguro que se hace aún más difícil.
Esperamos ayudar un poquito reabriendo el blog.
No puedo aseguraros que vayamos a contestar dudas, porque ya ninguno estamos en el día a día de un programador.


Esto sólo es un granito de arena en medio de la que está cayendo, pero menos es nada :-)

Espero de verdad que estéis todos bien y que salgamos de ésta fortalecidos.


Un abrazo muy fuerte a todos,

Talli.


lunes, 16 de febrero de 2015

Optimizando código

Recientemente hemos pasado por un proceso de optimización en la empresa donde trabajamos para intentar ahorrar MIPs y pagarle menos a IBM ;-)

Para ello el departamente de calidad se puso a escudriñar nuestras transacciones online con el STROBE, para detectar los picos de consumo, tanto del DB2 como del cobol. Para los que nunca halláis visto un informe de STROBE, es una cosa tal que así (muuuy largo):


El informe  ** SQL CPU USAGE SUMMARY ** nos da información sobre el consumo de CPU que se va en SQL por componente. Nos saldrá el desglose de programas ejecutados y su consumo:


Con esto ya podemos ir a tiro fijo a por los programas con más "cruces" :P

Hay otro apartado que se llama ** CPU USAGE BY SQL STATEMENT ** donde para cada programa te analiza sus accesos DB2:


En este ejemplo podéis ver que el programa CONSUL67 realiza la SELECT que viene indicada. En las estadísticas la columna STMT CNT nos indica que se realizan 1.218 selects (unas cuantas).
Aquí podríamos tener un problema de accesos recurrentes que podrían no ser necesarios.

En este otro ejemplo vemos un programa con varios cursores:


Aquí de primeras nos llamaría la atención el segundo OPEN. Si lo comparamos con el tercero, vemos que 567 OPENs del cursor 10 consumen el 2,46%, mientras que 1.491 OPENs del cursor 09 consumen el 0,24%. Lo cual es bastante sospechoso.
En nuestro caso, la tabla del cursor 10 tenía el índice mal definido.

Puliendo cada uno de los programas con mayores consumos iremos optimizando nuestras transacciones.

Más cosas que pueden producir "altos" consumos:
  • Tablas muy accedidas con pocos registros (menos de 7 páginas) que deben cargarse en working en lugar de acceder cada vez que necesitamos consultar algo.
  • Movimientos de grandes áreas de memoria.
  • Inicializar grandes áreas de memoria repetidas veces. 
  • Evitar el uso de Initialize, especialmente para variables tipo tabla (definidas en working o en copy) puesto que el programa tiene que evaluar cada campo antes de asignarle el valor cero o espacios para saber si es numérico o alfanumérico, y eso lo hace para cada ocurrencia de la tabla (aunque el campo ya lo haya analizado en la ocurrencia anterior).
  • Demasiada profundidad en llamadas a programas (programa1 llama a programa 2 que llama a programa 3 que llama a programa 4 que....). Cambiar por llamadas más directas.
  • Variables/copys de working que no se estén utilizando en el programa y que aumentan el área de memoria innecesariamente.
  • Trabajar con tablas demasiado grandes (más de 10000 occurs/500KB).
  • En sentencias SELECT en las que sólo interesa conocer la existencia o no de un registro, siendo indiferente que exista 1 o n veces (lo importante es saber si al menos existe una vez) utilizar el FETCH FIRST 1 ROW ONLY para que en cuanto encuentre una coincidencia deje de buscar.

Accesos DB2 que no van por índice.
En este caso podemos ayudarnos de las estadísticas que genera el  EXPLAIN que nos dirá los índices que utilizan cada uno de los accesos de nuestro programa. 

DECLARE  CUR-CC CURSOR FOR                         
SELECT   COD_CAMPO1                                
        ,QNU_CAMPO2                            
        ,COD_CAMPO3                               
FROM     TABLA001                                   
WHERE    COD_CAMPO4 = :DCLTABLA001.001-COD-CAMPO4
ORDER    BY COD_CAMPO1, QNU_CAMPO2


>>> Explain info from DSN.PLAN_TABLE 2014-08-05-15.47.37.112305
    Access table DSN.TABLA001                                  
       using index DSN.IABLA0013 (1 COL)                        
       Intent share;                                            
       group member is DS1L                             

Esto significa que el cursor está yendo por 1 solo campo del índice 3 de la tabla. En nuestro caso este índice tiene 3 campos y solo estamos informando el primero. Si podemos afinar el WHERE informando los otros 2 campos (que no siempre es posible) mejoraríamos el acceso.
Si el explain nos indica que no se está usando ningún índice, tendremos que revisar ese acceso.

OJO! Aunque se codifique en el WHERE por = y exista un índice con esos campos, no siempre quiere decir que DB2 va a utilizar esos accesos. DB2 tiene un algoritmo muy complejo para decidir cual es el acceso óptimo a los datos que se necesitan. Recordad que es importante el orden de los campos del índice así como el hecho de que sea único (los índices secundarios no siempre sirven para mejorar accesos).

Animaros a ampliar esta información dejando un comentario o escribiendo al correo del consultorio : )








jueves, 26 de diciembre de 2013

DB2 - Sequences

Seguramente todos conocemos la posibilidad de definir una columna de una tabla con IDENTITY, para que de esa forma se nos autogeneren valores y no tengamos que preocuparnos de realizar el típico SELECT MAX. Otro método un poco más desconocido para realizar esto son las SEQUENCES.

Una SEQUENCE es un objeto almacenado que permite generar una secuencia de números en forma ascendente o descendente. Aunque hay similitudes con una columna IDENTITY, la principal diferencia es que mientras la columna IDENTITY se define dentro del ámbito de una tabla, una SEQUENCE no está ligada a una sola tabla, por lo que puede ser usada desde varias. Esto permite usar la SEQUENCE para generar una clave primaria y coordinarla entre registros de distintas tablas.

Las principales características de una secuencia son:
  • Garantiza valores únicos.
  • Genera valores de forma creciente o decreciente dentro de un rango.
  • Puede incrementar valores en más de 1 unidad
  • Si se produce un error en DB2 la secuencia se reconstruye a partir del log, lo que garantiza que se seguirán generando correctamente los valores tras un fallo.

 Aquí va un ejemplo de cómo crear una secuencia:

CREATE SEQUENCE EJEMPLO_SEQ
       START WITH 1
       INCREMENT BY 1

Indicamos que deseamos crear la secuencia EJEMPLO_SEQ que comenzará en 1 y se incrementará en una unidad.

Ahora, suponiendo que la secuencia no se ha usado todavía, vamos a generar el primer valor y lo usaremos para insertar en la columna ID de la tabla EJEMPLO_TAB1. Tenemos dos opciones, generar el valor en el INSERT o generar el primer valor en una variable y luego utilizarlo en el INSERT.

Ejemplo de generarlo directamente en el INSERT:

INSERT INTO EJEMPLO_TAB1 (ID, NOMBRE)
VALUES (NEXT VALUE FOR EJEMPLO_SEQ, 'LOBOC');

Ejemplo de generar el valor sobre una variable y luego usarla en el INSERT:

SELECT NEXT VALUE FOR EJEMPLO_SEQ INTO :VAR_SEQ;

INSERT INTO EJEMPLO_TAB1 (ID, NOMBRE)
VALUES (:VAR_SEQ, 'LOBOC');

Cuando generamos un valor para una secuencia con NEXT VALUE (aunque este se realice en un SELECT) ese valor es consumido, y la próxima vez que se solicite un valor se generará uno nuevo. Esto ocurre incluso cuando se produce un fallo en la sentencia que contiene el NEXT VALUE o cuando se realiza rollback.

Justamente una de las potencias de las secuencias, el que garanticen que un NEXT VALUE siempre genera un nuevo valor bajo cualquier circunstancia podría llegar a ser un inconveniente. Por ejemplo, imaginemos que en un proceso batch generamos un fichero de LOAD donde para cada registro generamos un valor con NEXT VALUE. Si el fichero tiene 100.000 registros, habremos incrementado la secuencia en 100.000. Luego por cualquier problema o error, ese fichero de LOAD no se llega a cargar y se descarta. Esos 100.000 valores ya se habrán consumido y se habrán quedado huérfanos. En ciertos casos, donde sea importante que los números sean correlativos y no existan "huecos" (ej: numeración de las facturas) o donde tengamos el ID bastante ajustado en tamaño y no queremos que se nos consuman más valores de los estrictamente necesarios, quizás el uso de SEQUENCES no sea lo más recomendado.

Para profundizar más en el tema de las SEQUENCES os remitimos al  SQL Reference (SC19-2983-03) de IBM

NOTA: Para conocer las secuencias que ya están definidas podemos usar la siguiente consulta:

SELECT * FROM SYSIBM.SYSSEQUENCES

Fuente: SQL Reference (SC19-2983-03) IBM

lunes, 16 de septiembre de 2013

Wait en JCL

Este artículo surge de un problema que se nos presentó en el trabajo cuando montábamos un proceso batch de mantenimiento de tablas:

Cuando dentro de un job ejecutamos un programa que modifica la base de datos, el commit que guarda los cambios se realiza al finalizar el job.
Si un paso posterior diese error, los cambios no se quedaría grabados (haría un rollback).

Existe también la opción de realizar commit dentro del programa. Esto es habitual en los procesos rearrancables, que hacen commit cada n registros tratados (que es nuestro caso).

Incluso así, puede que los cambios no se guarden inmediatamente. En mi caso, el problema venía porque se trabaja contra vistas de las tablas, y tarda X minutos hasta que se carga sobre la tabla en sí.

¿Qué ocurre si quiero hacerme una descarga de la tabla justo después de haberla modificado? Que la descarga no contiene los cambios.

Para solventar este problemilla hemos añadido un paso de espera, que mantiene el job "esperando" el tiempo que le indiquemos. Así damos margen al DB2 para que actualice nuestra tabla.
En nuestro caso existía ya un programa para hacer el wait (desconozco su contenido), pero hemos montado el ejemplo para uso general en cualquier instalación.

El paso de "wait" consiste en ejecutar un programa REXX que recorre un bucle hasta que transcurre el tiempo que le indiquemos por parámetro (en segundos).

Código REXX:
/* rexx */                      
parse upper arg tiempowait          
   etime = time(R)              
   do while etime < tiempowait      
      nop                        
      etime = time(E)            
   end                          
exit

Y aquí va un ejemplo de job
Código JCL:
//*****************************************************************
//* PASO QUE EJECUTA UN PROGRAMA QUE MODIFICA BBDD                *
//*****************************************************************
//PASO01 EXEC PGM=IKJEFT01,DYNAMNBR=20
//SYSTSIN DD *
  DSN SYSTEM(DSN1)
  RUN PROGRAM(PRUEBA5) PLAN(PRUEBA5)
//SYSOUT  DD SYSOUT=*
//SYSIN   DD *
/*
//*****************************************************************
//* RETARDAMOS LA EJECUCION 3 MINUTOS PARA QUE SE CONSOLIDEN LOS  *
//* CAMBIOS EN BBDD                                               *
//*****************************************************************
//REXWAIT EXEC PGM=IKJEFT01             
//*-------                              
//SYSPROC DD DISP=SHR,DSN=LIBRERIA.CONTU.REXX 
//SYSLIST DD SYSOUT=*                   
//SYSOUT DD SYSOUT=*                    
//SYSTSPRT DD SYSOUT=*                  
//SYSTSIN DD *                          
 PGMWAIT 180                               
/*                                      
//*
//*****************************************************************
//* PASO QUE REALIZA LA DESCARGA DE LA BBDD                       *
//*****************************************************************
//UNLOAD EXEC PGM=IKJEFT01,DYNAMNBR=20
//SYSTSPRT DD SYSOUT=*
//SYSTSIN DD *
  DSN SYSTEM(DSN1)
  RUN PROGRAM(DSNTIAUL) PLAN(DSNTIAUL)
//SYSPRINT DD SYSOUT=*
//SYSUDUMP DD SYSOUT=*
//SYSPUNCH DD SYSOUT=*
//SYSREC00 DD DSN=XXXX.UNLOAD.TBPRU00,
//            DISP=(NEW,CATLG,DELETE),UNIT=SYSDA,
//            SPACE=(CYL,(100,50),RLSE)
//SYSIN    DD *
   SELECT * FROM DSN1.TBPRU00
//

Ojo con poner el parámetro TIME en la cabecera del job!!! Hay que darle tiempo :P
Esperamos que os sirva de ayuda.

En este caso han sido los compañeros de MVSFORUMS los que nos han dado la idea. Muchas gracias!

viernes, 17 de mayo de 2013

DB2 - Funciones escalares para fechas

Las funciones escalares se aplican a valores únicos de entrada, y devuelven un resultado de valor único.

Aquí veremos las funciones que nos proporciona DB2 para manejar fechas.

DATE: La función DATE devuelve una fecha que se deriva de un valor.

 

Ej:  Obtenemos un date del string '2013-01-01'

SELECT DATE('2013-01-01')   
FROM SYSIBM.SYSDUMMY1;       


COL1          
----------    
2013-01-01      


TO_DATE: La función TO_DATE devuelve un valor de timestamp que se basa en la interpretación del string de entrada utilizando el formato especificado.

 

 Ej: Obtenemos un TIMESTAMP a partir de una fecha en formato de 8.


SELECT TO_DATE('20130101','YYYYMMDD')    
FROM SYSIBM.SYSDUMMY1;         


COL1                        
--------------------------  
2013-01-01-00.00.00.000000               


 
YEAR: La función YEAR devuelve la parte del año de un valor. El valor debe ser un string válido de una fecha o timestamp.



Ej: Obtenemos el año del string '2013-01-01'

SELECT YEAR('2013-01-01')
FROM SYSIBM.SYSDUMMY1;    


       COL1
-----------
       2013


MONTH: La función MONTH devuelve la parte del mes de un valor. El valor debe ser un string válido de una fecha o timestamp.

 

Ej: Obtenemos el mes del string '2013-02-01'

SELECT MONTH('2013-02-01')
FROM SYSIBM.SYSDUMMY1;    


       COL1
-----------
          2


ADD_MONTHS: La función ADD_MONTHS devuelve una fecha resultado de sumar un número de meses a la fecha pasada como argumento. 



Ej: Sumamos 12 meses a la fecha '2013-02-01'

SELECT ADD_MONTHS('2013-02-01', 12)  
FROM SYSIBM.SYSDUMMY1;  
                         

COL1     
----------
2014-02-01

MONTHS_BETWEEN: La función MONTHS_BETWEEN devuelve el número estimado de meses entre dos fechas pasadas como argumento.



Ej: Vemos los meses que hay entre '2013-02-01' y 2014-02-01'

SELECT MONTHS_BETWEEN('2013-02-01','2014-02-01')
FROM SYSIBM.SYSDUMMY1;                          
               

                              COL1
----------------------------------
               -12.000000000000000

WEEK: La función WEEK devuelve un número entero comprendido entre 1 y 54 que representa la semana del año. Para el cómputo de una semana se comienza en Domingo.



Ej: Vemos que semana corresponde con la fecha '2012-01-01'

SELECT WEEK('2012-01-01')
FROM SYSIBM.SYSDUMMY1;           

                                   
       COL1
-----------
          1

WEEK_ISO: La función WEEK_ISO devuelve un número entero comprendido entre 1 y 53 que representa la semana del año. Para el cómputo de una semana se comienza en Lunes e incluye los 7 días.



Ej: Vemos que semana corresponde con la fecha '2012-01-01'

SELECT WEEK_ISO('2012-01-01')
FROM SYSIBM.SYSDUMMY1;          

                                   
       COL1
-----------
         52

DAY: La función DAY devuelve la parte del día de un valor. El valor debe ser un string válido de una fecha o timestamp, o bien un númerico que represente una fecha o timestamp.



Ej: Vemos que día corresponde con la fecha '2012-02-01'

SELECT DAY('2012-02-01')
FROM SYSIBM.SYSDUMMY1;          

                                   
       COL1
-----------
          1

DAYOFMONTH: La función DAYOFMONTH devuelve la parte del día de un valor. El valor debe ser un string válido de una fecha o timestamp.


 
Ej: Vemos que día del mes corresponde con la fecha '2012-02-01'

SELECT DAYOFMONTH('2012-02-01')
FROM SYSIBM.SYSDUMMY1;             

                                   
       COL1
-----------
          1

DAYOFWEEK: La función DAYOFWEEK devuelve un número entero del 1 al 7 que representa el día de la semana, tomando como día 1 el Domingo y 7 el Sábado.



Ej: Vemos que día de la semana corresponde con la fecha '2012-02-01'

SELECT DAYOFWEEK('2012-02-01')
FROM SYSIBM.SYSDUMMY1;                   

                                   
       COL1
-----------
          4

DAYOFWEEK_ISO: La función DAYOFWEEK_ISO devuelve un número entero del 1 al 7 que representa el día de la semana, tomando como día 1 el Lunes y 7 el Domingo.



Ej: Vemos que día de la semana corresponde con la fecha '2012-02-01'

SELECT DAYOFWEEK_ISO('2012-02-01')
FROM SYSIBM.SYSDUMMY1;                   

                                   
       COL1
-----------
          3

DAYOFYEAR: La función DAYOFYEAR devuelve un número entero del 1 al 366 que representa el día del año, tomando como día 1 el 1 de Enero.



Ej: Vemos que día del año corresponde con la fecha '2012-02-01'

SELECT DAYOFYEAR('2012-02-01')
FROM SYSIBM.SYSDUMMY1;                   

                                   
       COL1
-----------
         32

DAYS: La función DAYS devuelve un número entero que representa la fecha. El valor 1 respresenta el 1 del 1 de 0001.



Ej: Vemos que entero corresponde con la fecha '2012-02-01'

SELECT DAYS('2012-02-01')
FROM SYSIBM.SYSDUMMY1;                   

                                   
       COL1
-----------
     734534

JULIAN_DAY: La función JULIAN_DAY devuelve un número entero que representa el número de días desde el 1 de Enero de 4713 A.C.



Ej: Vemos que día juliano corresponde con la fecha '2012-02-01'

SELECT JULIAN_DAY('2012-02-01')
FROM SYSIBM.SYSDUMMY1;                   

                                   
       COL1
-----------
    2455959

LAST_DAY: La función LAST_DAY devuelve una fecha que representa el último día del mes pasado por argumento.



Ej: Vemos que fecha corresponde con el último día de la fecha '2012-02-01'

SELECT LAST_DAY('2012-02-01')
FROM SYSIBM.SYSDUMMY1;      

COL1     
----------
2012-02-29

NEXT_DAY: La función NEXT_DAY devuelve un timestamp que representa el siguiente día, al pasado por argumento, que se corresponde al día de la semana indicado por argumento.



Ej: Vemos que fecha se corresponde al siguiente Lunes a la fecha '2013-05-17'

SELECT NEXT_DAY('2013-05-17','MONDAY')
FROM SYSIBM.SYSDUMMY1;                 

COL1                    
--------------------------
2013-05-20-00.00.00.000000

Esperamos no dejarnos ninguna de las más habituales, de todas formas os recomendamos para profundizar en las funciones consultar el SQL Reference de IBM para DB2 10 z/OS.

Fuentes: SQL Reference (SC19-2983-03) IBM y Wikipedia