NUMBERs, JOINs y ORs…

2 marzo \02\UTC 2017

Desde la versión 14, Teradata implementa el tipo “Oracle” NUMBER. Y lo hace a su imagen y semejanza: con el almacenamiento en forma de exponente y mantisa. El motivo es, principalmente, facilitar las migraciones desde Bases de Datos Oracle a Teradata (de las que he visto unas cuantas y he participado en otras). Anteriormente, al no disponer Teradata de un tipo análogo al NUMBER de Oracle, había que elegir entre los numéricos “nativos”, con las dificultades que ello conlleva y la propensión a errores por elecciones incorrectas.

El tipo en sí presenta ciertas ventajas y varias particularidades, como el espacio de almacenamiento variable (de 2 a 18 bytes), la posibilidad de variar escala y precisión sin modificar las filas o la precisión de 18 dígitos (o incluso más) en las operaciones con ellos. Por otra parte, es un tipo en coma flotante (floating point) que puede no ser todo lo exacto que necesitamos y puede llevar a pequeñas incorrecciones en los cálculos.

Así que, en teoría, no hay ningún problema para utilizar NUMBER(n, m) en Teradata tal como lo hacemos en Oracle, donde es el tipo numérico por excelencia.

Pero a veces las cosas no son lo que parecen:

 BTEQ -- Enter your SQL request or BTEQ command:
SELECT * FROM DBC.DBCINFO;


 *** Query completed. 3 rows found. 2 columns returned.
 *** Total elapsed time was 1 second.

InfoKey                        InfoData
------------------------------ --------------------------------------
RELEASE                        15.10.02.01
VERSION                        15.10.02.03
LANGUAGE SUPPORT MODE          Standard

 BTEQ -- Enter your SQL request or BTEQ command:


CREATE MULTISET TABLE TESTDB.TEST1 (
   ID_C CHAR(2) NOT NULL,
   ID_N NUMBER
)
PRIMARY INDEX (ID_C);


 *** Table has been created.
 *** Total elapsed time was 1 second.


 BTEQ -- Enter your SQL request or BTEQ command:


INSERT INTO TESTDB.TEST1 VALUES ('98', 999999999999998);


 *** Insert completed. One row added.
 *** Total elapsed time was 1 second.


 BTEQ -- Enter your SQL request or BTEQ command:
INSERT INTO TESTDB.TEST1 VALUES ('99', 999999999999999);


 *** Insert completed. One row added.
 *** Total elapsed time was 1 second.


 BTEQ -- Enter your SQL request or BTEQ command:


CREATE MULTISET TABLE TESTDB.TEST2 (
   ID_C CHAR(2) NOT NULL,
   ID_N NUMBER(15, 0),
   C_TXT CHAR(6)
)
PRIMARY INDEX (ID_C);


 *** Table has been created.
 *** Total elapsed time was 1 second.


 BTEQ -- Enter your SQL request or BTEQ command:


INSERT INTO TESTDB.TEST2 VALUES ('98', 999999999999998, 'OCHO');


 *** Insert completed. One row added.
 *** Total elapsed time was 1 second.


 BTEQ -- Enter your SQL request or BTEQ command:
INSERT INTO TESTDB.TEST2 VALUES ('99', 999999999999999, 'NUEVE');


 *** Insert completed. One row added.
 *** Total elapsed time was 1 second.


 BTEQ -- Enter your SQL request or BTEQ command:


SELECT c.C_TXT
      ,a.ID_C
      ,a.ID_N
  FROM ( SELECT ID_C
               ,ID_N
          FROM TESTDB.TEST1
         WHERE ( ID_C = '98' AND
                 ID_N = 999999999999998 )
            OR ( ID_C = '99' AND
                 ID_N = 999999999999999 )
       ) a
  LEFT JOIN TESTDB.TEST2 c
         ON ( a.ID_C = c.ID_C
          AND a.ID_N = c.ID_N )
;


 *** Query completed. 2 rows found. 3 columns returned.
 *** Total elapsed time was 1 second.

C_TXT   ID_C                                      ID_N
------  ----  ----------------------------------------
(null)  99                             999999999999999
OCHO    98                             999999999999998

 BTEQ -- Enter your SQL request or BTEQ command:


SELECT c.C_TXT
      ,a.ID_C
      ,a.ID_N
  FROM ( SELECT ID_C
               ,ID_N
          FROM TESTDB.TEST1
         WHERE ( ID_C = '99' AND
                 ID_N = 999999999999999 )
            OR ( ID_C = '98' AND
                 ID_N = 999999999999998 )
       ) a
  LEFT JOIN TESTDB.TEST2 c
         ON ( a.ID_C = c.ID_C
          AND a.ID_N = c.ID_N )
;


 *** Query completed. 2 rows found. 3 columns returned.
 *** Total elapsed time was 1 second.

C_TXT   ID_C                                      ID_N
------  ----  ----------------------------------------
(null)  98                             999999999999998
NUEVE   99                             999999999999999

 BTEQ -- Enter your SQL request or BTEQ command:

Como podemos ver, la misma query da resultados diferentes sólo con cambiar el orden de los filtros en la parte WHERE … OR de la subselect “a”.

¡Pero eso es imposible! Todos sabemos que el ‘OR’ es conmutativo.

Además, ambos resultados son erróneos.

¿Y qué puede causar este comportamiento? Pues, presumiblemente, los resultados intermedios de la subselect “a” varían en cuanto al tipo elegido según lleguen las primeras filas como 999999999999998 ó 999999999999999. El tipo definido para ID_N en TEST1 es NUMBER, sin especificar escala ni precisión, mientras que en TEST2 es NUMBER(15, 0). Aunque externamente “NUMBER es NUMBER”, en algún lugar se producen diferencias que hacen que el JOIN no funcione como se espera.

Por eso, si estamos en lo cierto, al hacer un CAST explícito sobre las columnas NUMBER en la subselect todo debería funcionar OK:

SELECT c.C_TXT
      ,a.ID_C
      ,a.ID_N
  FROM ( SELECT ID_C
               ,ID_N (NUMBER(15,0))  --CAST!!
          FROM TESTDB.TEST1
         WHERE ( ID_C = '98' AND
                 ID_N = 999999999999998 )
            OR ( ID_C = '99' AND
                 ID_N = 999999999999999 )
       ) a
  LEFT JOIN TESTDB.TEST2 c
         ON ( a.ID_C = c.ID_C
          AND a.ID_N = c.ID_N )
;


 *** Query completed. 2 rows found. 3 columns returned.
 *** Total elapsed time was 1 second.

C_TXT   ID_C              ID_N
------  ----  ----------------
NUEVE   99     999999999999999
OCHO    98     999999999999998

 BTEQ -- Enter your SQL request or BTEQ command:


SELECT c.C_TXT
      ,a.ID_C
      ,a.ID_N
  FROM ( SELECT ID_C
               ,ID_N (NUMBER(15,0))  --CAST!!
          FROM TESTDB.TEST1
         WHERE ( ID_C = '99' AND
                 ID_N = 999999999999999 )
            OR ( ID_C = '98' AND
                 ID_N = 999999999999998 )
       ) a
  LEFT JOIN TESTDB.TEST2 c
         ON ( a.ID_C = c.ID_C
          AND a.ID_N = c.ID_N )
;


 *** Query completed. 2 rows found. 3 columns returned.
 *** Total elapsed time was 1 second.

C_TXT   ID_C              ID_N
------  ----  ----------------
NUEVE   99     999999999999999
OCHO    98     999999999999998

 BTEQ -- Enter your SQL request or BTEQ command:

En efecto, vemos que con CAST explícito todo vuelve a la normalidad y la SELECT da el resultado esperado (y correcto esta vez).

Claro que esto no deja de ser un “workaround“. Supongo que alguien tiene trabajo en Rancho Bernardo.

Por otra parte, siempre se debería especificar escala y precisión en los tipos NUMBER (en Oracle también).

Saludos.

Carlos.


Cargar campos “COBOL BINARY” con Multiload.

22 febrero \22\UTC 2017

Análogamente a los tipos COBOL ‘packed decimal’ que corresponden con tipos SQL DECIMAL(n, m), los tipos BINARY corresponden a tipos enteros SQL: SMALLINT (2 bytes), INTEGER (4 bytes) y BIGINT (8 bytes).

En multiload, definiendo los .FIELD adecuados en el .LAYOUT conseguiremos una carga sin errores:

...
.LAYOUT THE_LAYOUT;
...
.FIELD binary_field2  SMALLINT;
.FIELD binary_field4  INTEGER;
.FIELD binary_field8  BIGINT;
...

Pero ¿qué ocurre cuando el ‘PIC’ del campo BINARY indica que tiene decimales? Como en el caso de los ‘packed decimals‘ los decimales no se guardan en los campos BINARY, sino que se resuelven con los ‘PICTURES’ (PIC). Así pues, nos podemos encontrar, por ejemplo, un BINARY con un PIC S9(3)V9(6).
A diferencia de con los ‘packed decimals’ o los BINARY enteros, no podemos apoyarnos en la funcionalidad de multiload para la conversión directa, ya que no hay un tipo ‘SQL’ equivalente para BINARY con decimales.

Por eso lo debemos solucionar en dos pasos:

Primero definimos el .FIELD adecuado en el .LAYOUT. Si el campo es, por ejemplo, BINARY PIC S9(3)V9(6) entonces ocupará 4 bytes, y por tanto corresponderá a un tipo INTEGER. Así pues, en el ‘script‘ de multiload:

...
.LAYOUT THE_LAYOUT;
...
.FIELD binary_field4  INTEGER;
...

(Recordemos que según el tamaño del campo deberíamos haberlo definido como SMALLINT, INTEGER o BIGINT).

Una vez hecho esto deberemos insertarlo en la columna de la tabla correspondiente (que deberá ser DECIMAL(9, 6)). Es aquí donde haremos el CAST (implícito).

Más abajo en el mismo ‘script‘ de multiload, haciendo:

...
INSERT INTO MY_DB.MY_TABLE( ..., MY_COLUMN, ...) 
VALUES(..., :binary_field4/1000000.000000, ...);
...

conseguimos que el :binary_field4, que hemos cargado como INTEGER según el .LAYOUT se almacene en la columna como DECIMAL(9, 6) obedeciendo al ‘PIC’. Es muy importante definir el denominador con el número de decimales adecuado (en nuestro caso seis), porque si no se producirá un CAST erróneo que provocará truncamiento y nos dará un resultado incorrecto.

Saludos.

Carlos.


AWS Technical Professional

15 diciembre \15\UTC 2016

Pues resulta que soy Amazon Web Services Technical Professional certificado. 

¿Que si sirve para algo? Pues no sé… 

Saludos. 

Carlos. 


Cargando ceros binarios en un campo “packed decimal” como nulos.

8 noviembre \08\UTC 2016

Siguiendo con los campos “COBOL Comp-3 (Packed Decimals)” y Multiload, se nos ha presentado un problema a la hora de cargar ficheros en un HOST IBM.

El asunto consistía en que había un campo “packed decimal” con tipo DECIMAL(9,0). Siguiendo lo visto aquí, hicimos un script:

.LAYOUT DATAIN;
  .FIELD field_1
  ...
  .FIELD field_n * DECIMAL(9,0);
  ...

Pero es que, además, nos dijeron que deberíamos cargar un NULL si el campo “llegaba a cero”.

OK. No problem. Hay dos alternativas. En la definición del “layout” haciendo:

.LAYOUT DATAIN;
  ...
  .FIELD field_n * DECIMAL(9,0) NULLIF field_n=0;
  ...
  
o, sin el NULLIF del LAYOUT, resolviendo en la parte del INSERT:

   INSERT INTO ...
   ...
   COLUMN_N,
   ...
   )VALUES(
   ...
   NULLIFZERO(:field_n),
   ...
   );

Los problemas se presentaron cuando comenzaron a aparecer errores en las ejecuciones de multiload.
Cuando se utilizaba la opción 1 (NULLIF en el LAYOUT) multiload se quejaba con:

"UTY2203 Unable to process input data: INVALID DECIMAL DATA DURING COMPARISON."

Y cuando se usaba la opción 2 (NULLIFZERO) las filas iban a la tabla de errores ET_ reportando errores:

2679 "The format or data contains a bad character."

Tras los lógicos primeros momentos de estupefacción, echamos un vistazo al fichero con un editor hexadecimal y vimos que en el campo problemático no estaba llegando un “packed decimal” cero, sino que estaban llegando cinco bytes ceros binarios.
Ahí está el problema. Según vimos aquí, el formato de un “packed decimal” incluye un último “nibble” para el signo (C, D, o F), y en nuestro caso el signo brilla por su ausencia… Multiload simplemente no puede hacer el CAST(), pues lo que le hemos dicho que es un “packed decimal” no lo es.

Así que nos pusimos a pensar y pensar, y finalmente dimos con una solución.

Si duplicamos el campo con dos tipos diferentes (el DECIMAL(9,0) y uno previo auxiliar como BYTE(5)), tratando el campo ‘bueno’ (DECIMAL(9,0)) con un NULLIF sobre el campo ‘BYTE(5)’ se evita el CAST problemático y el multiload funciona OK. Hay que tener en cuenta que en el LAYOUT necesitamos fijar explícitamente la posición del campo en el fichero (en el ejemplo, posición nnn).

En el .LAYOUT hacemos:

      .FIELD field_n_b nnn BYTE(5);
      .FIELD field_n   nnn DECIMAL(9,0) NULLIF field_n_b='0000000000'xB;

En el INSERT no hace falta el NULLIFZERO() porque el campo ya fue tratado en el LAYOUT:

Esto funciona también en entornos Linux/UNIX/Windows.

Si tenemos una tabla:

CREATE MULTISET TABLE CARLOS.PRUEBA_NULLIF_DATE
   (
      ID_N   INTEGER NOT NULL,
      D_DATE DATE
   )
;

Y un fichero en el el campo de fecha puede traer ceros binarios (10 bytes 0x00, representados por · ),

12016-11-07
22016-11-08
3··········          
42016-11-09

podemos cargarlo haciendo un script multiload:

.DATEFORM ANSIDATE; 

.LOGTABLE CARLOS.PRUEBA_NULLIF_DATE_LOG;

.LOGON MY_DB/carlos,**********;

.BEGIN IMPORT MLOAD TABLES CARLOS.PRUEBA_NULLIF_DATE;

.DML LABEL INSERTAR_PRUEBA_NULLIF_DATE;
INSERT INTO CARLOS.PRUEBA_NULLIF_DATE VALUES(
	:id_n,
	:d_date)
;

.LAYOUT INSERTAR;
	.FIELD id_n     1 CHAR(1);
        .FIELD d_date_b 2 BYTE(10);
	.FIELD d_date   2 DATE NULLIF d_date_b = '00000000000000000000'xB;

.IMPORT INFILE .\Ficheros\Prueba_NULLIF_DATE.txt FORMAT TEXT
 LAYOUT INSERTAR
  APPLY INSERTAR_PRUEBA_NULLIF_DATE;

.END MLOAD;

.LOGOFF;

Lo que subyace es el hecho de que el NULLIF se resuelve con el FIELD tipo BYTE anterior ANTES de hacer el CAST del campo sobre el que se actúa, con lo que ni siquiera se hace el CAST y no falla en la conversión de tipos.

Saludos.

Carlos.


Cargar campos “COBOL Comp-3 (Packed Decimals)” con Multiload.

6 octubre \06\UTC 2016

Teradata permite conexión directa entre entornos “mainframe” y sistemas Teradata mediante conexiones FICON. Esto significa que se pueden instalar las TTU’s en dichos “mainframes” y cargar directamente datos desde ficheros que existan allí sin necesidad de hacer ftp’s a entornos UNIX, Linux o Windows, con lo engorroso que puede ser el ftp en sí, por no hablar de las conversiones EBCDIC / ASCII…

El problema viene cuando los ficheros en cuestión contienen campos “Comp-3” (Computational-3), también llamados “packed decimals“.

Pero ¿qué son exactamente estos “Comp-3”?

“Comp-3” es una forma de almacenamiento de datos numéricos en COBOL en formato BCD (binary coded decimal). El BCD almacena numeros en formato DECIMAL y,como los números decimales van de 0 a 9, caben en cuatro bits (es lo que se llama un ‘nibble‘) con lo que en un byte caben dos números (ej.: byte 11, en bin 00010001 => almacena el 11 decimal). Esto hace que los datos ocupen la mitad de lo que ocuparían como caracteres (un byte por cada dígito).

A esto hay que sumarle que el último nibble (el menos significativo) se guarda para el signo, y se notan como “C” hex para positivos, “D” hex para negativos y “F” hex para “unsigned“. Así el número 1000 se guardará en tres bytes como 01 00 0C.

Para calcular lo que ocupará un número en Comp-3 bastará con contar sus dígitos, sumarle uno para el signo y dividir por dos. En caso de que el resultado no sea entero se redondea hacia arriba. En nuestro ejemplo anterior: 1000 => (4+1)/2 = 2,5 y se redondea a 3.

OK, pero ¿cómo se guardan los decimales? pues NO se guardan, sino que se resuelven con los ‘PICTURES’ (PIC). Siguiendo con nuestro ejemplo, si el campo es definido como ‘PIC S9(4)’, será 1000, pero si lo definimos como PIC S9(2)V99 se convertirá en 10.00.

Vale. Ahora ya sabemos lo que son pero ¿cómo los cargamos con Multiload?

Un poco de calma. Antes vamos ver cómo nosotros linuxeros y windowseros hemos adquirido vicios a la hora de cargar ficheros, en este caso con multiload, y más tarde nos daremos cuenta de que las cosas son más fáciles de lo que parecen…

¿Qué hacemos normalmente para cargar ficheros? Pues normalmente elegimos entre ficheros “TEXT” (de registros de longitud fija) y ficheros “VARTEXT” (registros de longitud variable con separadores de campo). Y a la hora de definir el LAYOUT definimos generalmente campos CHAR() o VARCHAR() donde colocar la información de los campos del fichero, que suelen llegar en forma de texto (ASCII, o UTF8, o lo que sea…). El objeto de esto es que el LAYOUT guarde la información en texto y que los cambios de tipo (CASTs) se resuelvan a la hora de la inserción final en las tablas.

Esto funciona generalmente muy bien, pero hace que olvidemos cómo funciona el multiload (en realidad el dataconector o “PIOM” para los más viejos).

Vamos a ver un ejemplo:

Tenemos un fichero Prueba.txt de longitud fija (4 bytes por registro) que tiene:

1UNO
2DOS

Tenemos una tabla para cargar dicho fichero:

 BTEQ -- Enter your SQL request or BTEQ command: 
DROP TABLE CARLOS.PRUEBA_TEXT;
CREATE MULTISET TABLE CARLOS.PRUEBA_TEXT
   (
      ID_N  INTEGER NOT NULL,
      C_TXT CHAR(3)
   )
;

 *** Failure 3807 Object 'CARLOS.PRUEBA_TEXT' does not exist.
                Statement# 1, Info =0 
 *** Total elapsed time was 1 second.


 BTEQ -- Enter your SQL request or BTEQ command: 


 *** Table has been created. 
 *** Total elapsed time was 1 second.

Creamos el script de multiload:

.LOGTABLE CARLOS.PRUEBA_TEXT_LOG;

.LOGON MyTdpId/carlos,MyPassword;

.BEGIN IMPORT MLOAD TABLES CARLOS.PRUEBA_TEXT;

.DML LABEL INSERTAR_PRUEBA_TEXT;
INSERT INTO CARLOS.PRUEBA_TEXT VALUES(
	:id_n,
	:c_txt)
;

.LAYOUT INSERTAR;
	.FIELD id_n    1 CHAR(1);
	.FIELD c_txt   * CHAR(3);

.IMPORT INFILE Prueba.txt FORMAT TEXT
 LAYOUT INSERTAR
  APPLY INSERTAR_PRUEBA_TEXT;

.END MLOAD;

.LOGOFF;

Y lo ejecutamos. Acaba (sin errores):

...
     Target table 1: CARLOS.PRUEBA_TEXT
     Number of Rows        Error Table Name
     ====================  ==================================================
                        0  CARLOS.ET_PRUEBA_TEXT
                        0  CARLOS.UV_PRUEBA_TEXT

**** 19:19:13 UTY0817 MultiLoad submitting the following request:
     BEGIN TRANSACTION;
**** 19:19:13 UTY0817 MultiLoad submitting the following request:
     USING Ckpt(VARBYTE(1024)) INS CARLOS.PRUEBA_TEXT_LOG (LogType, Seq,
     MLoadCkpt)VALUES(140, 1, :Ckpt);
**** 19:19:13 UTY0817 MultiLoad submitting the following request:
     INS CARLOS.PRUEBA_TEXT_LOG (LogType, Seq) VALUES (125, 1)
**** 19:19:13 UTY0817 MultiLoad submitting the following request:
     END TRANSACTION;
**** 19:19:13 UTY0822 MultiLoad processing complete for this MultiLoad import
     task.
     ========================================================================
     =                                                                      =
     =          MultiLoad Task Complete                                     =
     =                                                                      =
     ========================================================================
**** 19:19:13 UTY1024 Session modal request, 'SET
     QUERY_BAND='UTILITYNAME=MULTLOAD;' UPDATE FOR SESSION;', re-executed.
     ========================================================================
     =                                                                      =
     =          Processing Control Statements                               =
     =                                                                      =
     ========================================================================

0011 .LOGOFF;
     ========================================================================
     =                                                                      =
     =          Logoff/Disconnect                                           =
     =                                                                      =
     ========================================================================
**** 19:19:14 UTY6216 The restart log table has been dropped.
**** 19:19:14 UTY6212 A successful disconnect was made from the RDBMS.
**** 19:19:14 UTY2410 Total processor time used = '0.265202 Seconds'
     .       Start : 19:19:04 - THU OCT 06, 2016
     .       End   : 19:19:14 - THU OCT 06, 2016
     .       Highest return code encountered = '0'.

Si vemos lo que hemos cargado:

 BTEQ -- Enter your SQL request or BTEQ command: 
SELECT * FROM CARLOS.PRUEBA_TEXT ORDER BY 1;


 *** Query completed. 2 rows found. 2 columns returned. 
 *** Total elapsed time was 1 second.

       ID_N  C_TXT
-----------  -----
          1  UNO
          2  DOS

Todo correcto, pues. Pero vamos a cambiar un poco el script de multiload. Vamos a cambiar en el LAYOUT el campo id_n de CHAR(1) a DECIMAL(1) (que también ocupa 1 byte):

.LOGTABLE CARLOS.PRUEBA_TEXT_LOG;

.LOGON MyTdpId/carlos,MyPassword;

.BEGIN IMPORT MLOAD TABLES CARLOS.PRUEBA_TEXT;

.DML LABEL INSERTAR_PRUEBA_TEXT;
INSERT INTO CARLOS.PRUEBA_TEXT VALUES(
	:id_n,
	:c_txt)
;

.LAYOUT INSERTAR;
	.FIELD id_n    1 DECIMAL(1);
	.FIELD c_txt   * CHAR(3);

.IMPORT INFILE Prueba.txt FORMAT TEXT
 LAYOUT INSERTAR
  APPLY INSERTAR_PRUEBA_TEXT;

.END MLOAD;

.LOGOFF;

Una nueva ejecución (otra vez sin errores):

     Target table 1: CARLOS.PRUEBA_TEXT
     Number of Rows        Error Table Name
     ====================  ==================================================
                        0  CARLOS.ET_PRUEBA_TEXT
                        0  CARLOS.UV_PRUEBA_TEXT

**** 19:22:08 UTY0817 MultiLoad submitting the following request:
     BEGIN TRANSACTION;
**** 19:22:08 UTY0817 MultiLoad submitting the following request:
     USING Ckpt(VARBYTE(1024)) INS CARLOS.PRUEBA_TEXT_LOG (LogType, Seq,
     MLoadCkpt)VALUES(140, 1, :Ckpt);
**** 19:22:08 UTY0817 MultiLoad submitting the following request:
     INS CARLOS.PRUEBA_TEXT_LOG (LogType, Seq) VALUES (125, 1)
**** 19:22:08 UTY0817 MultiLoad submitting the following request:
     END TRANSACTION;
**** 19:22:08 UTY0822 MultiLoad processing complete for this MultiLoad import
     task.
     ========================================================================
     =                                                                      =
     =          MultiLoad Task Complete                                     =
     =                                                                      =
     ========================================================================
**** 19:22:08 UTY1024 Session modal request, 'SET
     QUERY_BAND='UTILITYNAME=MULTLOAD;' UPDATE FOR SESSION;', re-executed.
     ========================================================================
     =                                                                      =
     =          Processing Control Statements                               =
     =                                                                      =
     ========================================================================

0011 .LOGOFF;
     ========================================================================
     =                                                                      =
     =          Logoff/Disconnect                                           =
     =                                                                      =
     ========================================================================
**** 19:22:09 UTY6216 The restart log table has been dropped.
**** 19:22:09 UTY6212 A successful disconnect was made from the RDBMS.
**** 19:22:09 UTY2410 Total processor time used = '0.296402 Seconds'
     .       Start : 19:22:00 - THU OCT 06, 2016
     .       End   : 19:22:09 - THU OCT 06, 2016
     .       Highest return code encountered = '0'.

Verificamos los datos en la tabla:

 BTEQ -- Enter your SQL request or BTEQ command: 
SELECT * FROM CARLOS.PRUEBA_TEXT ORDER BY 1;


 *** Query completed. 4 rows found. 2 columns returned. 
 *** Total elapsed time was 1 second.

       ID_N  C_TXT
-----------  -----
          1  UNO
          2  DOS
         49  UNO
         50  DOS

¡Hey! ¿Qué son ese 49 y ese 50? Pues ni más ni menos que los códigos decimales ASCII correspondientes a los dígitos ‘1’ y ‘2’. Al definir el campo (.FIELD) como DECIMAL(1) le hemos dicho que el dato que viene en el campo no es texto, sino un número, y multiload así lo hace: lee los BYTES como números y los carga en la tabla tal cual, sin interpretar el dato como la representación “en texto” de un número.

Pues sabiendo esto, y aunque no nos demos cuenta todavía, ahí está la clave para cargar los Comp-3 (Packed Decimals) en “mainframes“. La receta es fácil: si el dato numérico viene como texto (que será EBCDIC), bastará definir el .FIELD como CHAR(n) y multiload se encargará del CAST a numérico. Si por el contrario viene como “packed decimal” necesitaremos saber cuánto ocupa el campo en BYTES. En este caso también es necesario conocer el ‘PIC’ original. Así, por ejemplo:

PIC S9(4) COMP-3 => Byte size = (4 + 1) / 2 = 2.5 -> 3 bytes => .FIELD fld1 * DECIMAL(4)
PIC S9(5)V99 COMP-3 => Byte size = (5 + 2 + 1) / 2 = 4 bytes => .FIELD fld2 * DECIMAL(7,2)

Como se ve, el ‘PIC’ funciona de distinta manera que la definición de numéricos SQL: la parte entera se suma a la parte fraccionaria, mientras que en SQL la ESCALA supone el número total de dígitos, mientras que la PRECISIÓN dice cuántos de ellos son decimales.

Saludos.

Carlos.


Historial de comandos en bteq (y sql*plus) con las flechas del teclado en Linux.

8 junio \08\UTC 2016

La funcionalidad de utilizar las flechas del teclado para navegar por la historia de las sentencias ejecutadas en bteq (y en sql*plus) cuando trabajamos en entornos Windows hace que cuando nos pasamos a entornos Linux nos pase siempre esto:

 BTEQ -- Enter your SQL request or BTEQ command:
SELECT DATE;

 *** Query completed. One row found. One column returned.
 *** Total elapsed time was 1 second.

    Date
--------
16/06/08

 BTEQ -- Enter your SQL request or BTEQ command:
^[[A

¡Arrrggg! Y siempre nos pasa. Una y otra vez.

Pero hay una solución: utilizar rlwrap, que es un ‘readline wrapper‘. Basta con buscarlo (por ejemplo aquí) e instalarlo.

La instalación es simple: descomprimir el ‘tarball‘ (.tar.gz) en un directorio de trabajo y ejecutar:

./configure 
make install

Yo lo hago como ‘root’ para evitar problemas (“no se puede crear el directorio /usr/local/share/man”).

¡Ojo! Hay que tener instaladas las librerías GNU readline y readline-devel para que el instalador no termine con error.

Una vez hecho esto, lo que suelo hacer es modificar los lanzadores de bteq y sql*plus que tengo en el escritorio sustituyendo las llamadas simples “bteq” y “sqlplus /nolog” por “rlwrap bteq” y “rlwrap sqlplus /nolog”.

A partir de ese momento tengo mis utilidades SQL de línea de comando favoritas funcionando exactamente igual que en Windows y puedo navegar por la historia de los comandos ejecutados con “flecha arriba” y “flecha abajo”.

Saludos.

Carlos.


De registros, campos, filas y columnas…

15 abril \15\UTC 2016

Los que llaman “registros” y “campos” a las filas y columnas de las tablas de una base de datos son como los que llaman “reina” a la dama en ajedrez.

Columns are not fields. Rows are not records. Tables are not files.” – Joe Celko

Saludos.

Carlos.