Teradata Unity implementation workshop

10 enero \10\UTC 2015

Esta semana la he pasado haciendo el Teradata Unity implementation workshop. Un ‘hands on workshop‘ que te da la oportunidad de conocer, utilizar y experimentar con los diferentes productos del ecosystem de Teradata Unity y las interacciones entre ellos.

El curso ha sido interesantísimo y el monitor, además de saber un montón, ha resultado ser un tipo encantador.

Teradata Unity implementation workshop

Teradata Unity implementation workshop

Una semana muy bien aprovechada…

Saludos.

Carlos.


Teradata Studio 15 sobre Mac OS X Snow Leopard

27 noviembre \27\UTC 2014

Nunca he sido muy aficionado a los Mac (aunque en chez CarlosAL últimamente han empezado a aparecer algunos dispositivos con la manzanita…). Me parecen los más cerrados de los sistemas (incluso más que Windows) y aunque son muy ‘cool‘ (o eso suelen decir sus propietarios) siendo -como soy básicamente- un desarrollador, no me he topado con demasiada gente que desarrolle sobre Mac.

Y Teradata debe de ser de mi misma opinión, ya que no existen apenas herramientas de desarrollo para Mac: Ni cliV2, ni TTU’s… apenas un ‘driver ODBC’… y Teradata Studio.

Aunque no veo muchas razones por las que se pueda querer instalar Teradata Studio en un Mac. Sólamente se me ocurre el “porque sí” (just for the hell of it) o simplemente porque al Bicho Nº2 le encanta la foto del leopardo en la nieve.

En cualquier caso, teniendo un Mac OS X Snow Leopard con runtime de java JRE 1.6 no hay más que bajarse el .zip que contiene el .pkg y proceder a su instalación. Luego no hay más que configurar la conexión a Teradata exactamente igual que en las versiones de Windows o Linux y el resultado es:

TD Studio 15 on Mac OS X Snow Leopard

TD Studio 15 on Mac OS X Snow Leopard

Ahora, también es cierto que no creo que lo use mucho.

Saludos.

Carlos.


Teradata User Group (TUG) Madrid 2014

20 noviembre \20\UTC 2014

Ayer estuve en el Teradata User Group Madrid 2014 que se celebró ni más ni menos que en el estadio Santiago Bernabéu.

Al interés de las ponencias y de los conferenciantes se añadía el de la propia sede (al menos para algunos de los asistentes entre los que me encuentro).

TUG2014_1

TUG2014_1

Muy interesante fue la charla de Enrique Dans respecto a la infinidad de datos que generamos, su utilidad y los problemas de intimidad que pueden surgir (por favor, no uséis ‘privacidad’ en castellano: duelen los oídos).

Más interesante si cabe (por más técnica) fue la que ofrecieron “al alimón” Stephen Brobst (el auténtico guru de Teradata) y Duncan Ross sobre los errores a evitar a la hora de hacer análisis de big data. Además ofrecía la posibilidad de apreciar las diferencias entre el inglés yankee del primero y el muy británico del segundo.

El resto de las conferencias no desmereció tampoco (bueno, una sí, pero no diré cuál).

Finalmente pude charlar con compañeros, excompañeros, clientes y exclientes en un ambiente relajado en el cóctel que cerraba el acto. Estas charlas medio banales medio técnicas pueden devenir en conversaciones muy interesantes.

TUG2014_2

TUG2014_2

También hubo foto de familia en el mismísimo césped del Bernabéu. Hubo alguno que hasta se santiguó, aunque a algún colchonero -que también había- le salían ronchas sólo de estar allí ;-)

TUG2014_3

TUG2014_3

Saludos.

Carlos.


Copiando estadísticas en Teradata.

18 noviembre \18\UTC 2014

A partir de la versión 13 Teradata ofrece la posibilidad de copiar estadísticas de una tabla a otra. Esta funcionalidad se revela muy útil -por ejemplo- en las ocasiones en las que queremos analizar e investigar planes de ejecución mediante EXPLAIN sobre tablas pequeñas (p. ej. en un entorno de desarrollo) y queremos que el optimizador las considere como si fueran grandes (p. ej. de un entorno de producción).

Mediante la sentencia COLLECT STATISTICS … FROM … Teradata copiará los datos de estadísticas de una tabla sobre las de otra sin efectuar cálculo alguno:

COLLECT STATISTICS ON <db_name>.<table_name> 
FROM <db_name>.<table_name> COLUMN(<column_name>);

Tras ejecutar esta sentencia, el optimizador calculará los planes de ejecución basándose en los valores de las estadísticas copiadas y por tanto considerará los valores de la demografía de los datos como si los de la tabla origen se tratase.

Hay otras situaciones para las que también puede ser de utilidad: si creamos una tabla a partir de otra para efectuar algún tipo de modificación de estructura mediante el método de ejecutar un INSERT … SELECT y RENAME posterior (y evitarnos un ALTER TABLE) no tenemos por qué volver a generar las estadísticas para la nueva tabla al final del proceso, sino que podemos copiarlas directamente de la tabla origen mediante esta técnica y ahorrarnos bastante tiempo.

Saludos.

Carlos.


Borrando filas duplicadas en Teradata (II)

21 octubre \21\UTC 2014

Es un asunto recurrente en cualquier foro de Teradata: ¿cómo borro filas duplicadas en una tabla MULTISET? El tema ya lo tratamos aquí, y el problema principal es que Teradata no maneja los ROWIDs a la manera de Oracle (y otros RDBMSs), de forma que no se puede acceder a las filas directamente mediante su ROWID para borrarlas de forma individual.

Hay varias formas de librarse de las molestas filas duplicadas, como crear tablas nuevas y llenarlas con SELECT DISTINCT… También con técnicas algo más más artificiosas, como vimos aquí. Hoy vamos a ver otra solución al problema, aunque es un poco más enrevesada…

Tenemos una tabla MULTISET con filas repetidas:

 BTEQ -- Enter your SQL request or BTEQ command:
SHOW TABLE CARLOS.PRUEBADUP;


 *** Text of DDL statement returned.
 *** Total elapsed time was 1 second.

-------------------------------------------------------------------
CREATE MULTISET TABLE CARLOS.PRUEBADUP ,
     NO FALLBACK ,
     NO BEFORE JOURNAL,
     NO AFTER JOURNAL,
     CHECKSUM = DEFAULT,
     DEFAULT MERGEBLOCKRATIO
     (
      ID_N INTEGER NOT NULL,
      C_TXT VARCHAR(10) CHARACTER SET LATIN CASESPECIFIC NOT NULL)
PRIMARY INDEX ( ID_N );


 BTEQ -- Enter your SQL request or BTEQ command:
SELECT * FROM CARLOS.PRUEBADUP;


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

       ID_N  C_TXT
-----------  ----------
          1  UNO
          1  UNO
          1  UNO

Para eliminar los duplicados vamos a recurrir a un cursor y una cláusula WHERE CURRENT OF, que es ANSI 2011. Para ello creamos un procedimiento almacenado de la siguiente forma:

REPLACE PROCEDURE CARLOS.BORRADUPS()
BEGIN
   DECLARE iIndice INTEGER DEFAULT 1;
   FOR iFila AS cFila CURSOR FOR SELECT ID_N, C_TXT FROM CARLOS.PRUEBADUP
   DO
      IF iIndice > 1 THEN
         DELETE FROM CARLOS.PRUEBADUP
               WHERE CURRENT OF cFila;
      END IF;
      SET iIndice = iIndice + 1;
   END FOR;
END;

Así pues, lo compilamos…

 BTEQ -- Enter your SQL request or BTEQ command:
.COMPILE FILE C:\Carlos\TeradataStoredProcedures\BORRADUPS.sql;


 *** Procedure has been replaced.
 *** Total elapsed time was 1 second.


 BTEQ -- Enter your SQL request or BTEQ command:
COMMIT;


 *** COMMIT done.
 *** Total elapsed time was 1 second.

…y lo ejecutamos:

 BTEQ -- Enter your SQL request or BTEQ command:
CALL CARLOS.BORRADUPS();


 *** Procedure has been executed.
 *** Total elapsed time was 1 second.


 BTEQ -- Enter your SQL request or BTEQ command:
COMMIT;

 *** COMMIT done.
 *** Total elapsed time was 1 second.

Y, como por arte de magia, tenemos que:

 BTEQ -- Enter your SQL request or BTEQ command:
SELECT * FROM  CARLOS.PRUEBADUP;


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

       ID_N  C_TXT
-----------  ----------
          1  UNO

Las filas duplicadas han desaparecido de forma sencilla y limpia.

Aunque los más avispados se habrán extrañado al encontrarse con todos esos COMMITs… y con razón. Esto es así porque la cláusula WHERE CURRENT OF sólo funciona en sesiones en modo ANSI, por lo que antes de comenzar la sesión en bteq que compilaba el procedimiento almacenado hubo que ejecutar un:

.SET SESSION TRANSACTION ANSI

Y esto, aunque pueda parecer poco importante, tiene ciertas consecuencias, porque si abrimos otra sesión en modo BTET e intentamos ejecutar el procedimiento:

.LOGON SLES11/carlos
Password:

 *** Logon successfully completed.
 *** Teradata Database Release is 14.10.00.02
 *** Teradata Database Version is 14.10.00.02
 *** Transaction Semantics are BTET.
 *** Session Character Set Name is 'ASCII'.

 *** Total elapsed time was 1 second.

 BTEQ -- Enter your SQL request or BTEQ command:
 *** Warning: EOF on INPUT stream.
 BTEQ -- Enter your SQL request or BTEQ command:
CALL CARLOS.BORRADUPS();

 *** Failure 5510 Invalid session mode for procedure execution.
                Statement# 1, Info =0
 *** Total elapsed time was 1 second.

En efecto, un procedimiento almacenado que fue compilado en una sesión con modo ANSI no puede ser ejecutado en modo Teradata (BTET).

Well, nobody’s perfect! – Osgood Fielding III.

Por otra parte, un tratamiento row by row dista mucho de ser el tratamiento ideal (aunque, como siempre, puede haber determinados casos en los que pudiera ser aplicable, sobre todo definiendo bien el cursor y siempre y cuando no haya un gran número de filas duplicadas).

Saludos.

Carlos.


¿Cuántas tablas ves aquí?

7 octubre \07\UTC 2014

A veces, hasta el SQL más sencillo puede despistar a un observador poco avispado (“basado en hechos reales“).

 BTEQ -- Enter your SQL request or BTEQ command:
CREATE MULTISET TABLE CARLOS.PRUEBA01,
     NO FALLBACK ,
     NO BEFORE JOURNAL,
     NO AFTER JOURNAL,
     CHECKSUM = DEFAULT,
     DEFAULT MERGEBLOCKRATIO
     (
        ID_N  INTEGER     NOT NULL,
        C_TXT VARCHAR(15) NOT NULL
     )
     PRIMARY INDEX ( ID_N )
;


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


 BTEQ -- Enter your SQL request or BTEQ command:


INSERT INTO CARLOS.PRUEBA01 VALUES (1,'UNO');


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


 BTEQ -- Enter your SQL request or BTEQ command:
INSERT INTO CARLOS.PRUEBA01 VALUES (2,'DOS');


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


 BTEQ -- Enter your SQL request or BTEQ command:
INSERT INTO CARLOS.PRUEBA01 VALUES (3,'TRES');


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


 BTEQ -- Enter your SQL request or BTEQ command:


CREATE MULTISET TABLE CARLOS.PRUEBA02,
     NO FALLBACK ,
     NO BEFORE JOURNAL,
     NO AFTER JOURNAL,
     CHECKSUM = DEFAULT,
     DEFAULT MERGEBLOCKRATIO
     (
        ID_N  INTEGER     NOT NULL,
        C_TXT VARCHAR(15) NOT NULL
     )
     PRIMARY INDEX ( ID_N )
;


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


 BTEQ -- Enter your SQL request or BTEQ command:


INSERT INTO CARLOS.PRUEBA02 VALUES (1,'RAS');


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


 BTEQ -- Enter your SQL request or BTEQ command:
INSERT INTO CARLOS.PRUEBA02 VALUES (2,'DVA');


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


 BTEQ -- Enter your SQL request or BTEQ command:
INSERT INTO CARLOS.PRUEBA02 VALUES (3,'TRI');


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

Y ahora la ‘SELECT’ a la que hace referencia el título: “¿Cuántas tablas ves aquí?”

SELECT *
FROM CARLOS.PRUEBA01 t1,
CARLOS.PRUEBA02 t2
WHERE t1.ID_N = CARLOS.PRUEBA02.ID_N
;

La respuesta es clara ¿verdad?: Dos tablas.

Pues vamos a ver qué ocurre si ejecutamos la ‘query':

 BTEQ -- Enter your SQL request or BTEQ command:


SELECT *
  FROM CARLOS.PRUEBA01 t1,
       CARLOS.PRUEBA02 t2
 WHERE t1.ID_N = CARLOS.PRUEBA02.ID_N
;


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

       ID_N  C_TXT                   ID_N  C_TXT
-----------  ---------------  -----------  ---------------
          2  DOS                        3  TRI
          2  DOS                        2  DVA
          2  DOS                        1  RAS
          3  TRES                       3  TRI
          3  TRES                       2  DVA
          3  TRES                       1  RAS
          1  UNO                        3  TRI
          1  UNO                        2  DVA
          1  UNO                        1  RAS

¿Qué es este resultado? ¿Cómo es posible que falle un ‘join’ tan sencillo?

Pues la respuesta a esta pregunta y a la que da nombre a este ‘post’ es que en realidad hay TRES tablas en el ‘join’, ya que al incluir el nombre completo de la segunda tabla en el ‘WHERE’ (t1.ID_N = CARLOS.PRUEBA02.ID_N) estamos implícitamente incluyendo la tabla PRUEBA02 una segunda vez y provocando un CROSS JOIN que es el causante de los resultados tan extraños (algo parecido a esto lo vimos aquí hace tiempo).

Por supuesto que todo se soluciona si en el ‘WHERE’ hacemos referencia a la columna de PRUEBA02 mediante el alias que hemos declarado (t2):

 BTEQ -- Enter your SQL request or BTEQ command:
 SELECT *
  FROM CARLOS.PRUEBA01 t1,
       CARLOS.PRUEBA02 t2
 WHERE t1.ID_N = t2.ID_N;


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

       ID_N  C_TXT                   ID_N  C_TXT
-----------  ---------------  -----------  ---------------
          3  TRES                       3  TRI
          2  DOS                        2  DVA
          1  UNO                        1  RAS

La moraleja del cuento: o usamos nombres cualificados completos o utilizamos alias, pero mejor no mezclarlos…

Saludos.

Carlos.


Sorpresas con comparaciones en columnas TIMESTAMP WITH DEFAULT

18 septiembre \18\UTC 2014

A veces te encuentras sorpresas en el día a día. Hoy di con algo sorprendente respecto de las comparaciones con timestamps:

Vamos a hacer una pequeña prueba de comparaciones de TIMESTAMPs con mayor estricto para colunas definidas con ‘WITH DEFAULT':

 BTEQ -- Enter your SQL request or BTEQ command:
CREATE MULTISET TABLE CARLOS.PRUEBA01
   ( ID_N INTEGER NOT NULL,
     TS_TIMESTAMP TIMESTAMP(0) NOT NULL WITH DEFAULT )
;


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


 BTEQ -- Enter your SQL request or BTEQ command:


INSERT INTO CARLOS.PRUEBA01(ID_N) VALUES ( 1 )
;INSERT INTO CARLOS.PRUEBA01(ID_N) VALUES ( 2 )
;INSERT INTO CARLOS.PRUEBA01(ID_N) VALUES ( 3 )
;


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

 *** Insert completed. One row added.

 *** Insert completed. One row added.

 BTEQ -- Enter your SQL request or BTEQ command:


SELECT *
  FROM CARLOS.PRUEBA01
;


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

       ID_N         TS_TIMESTAMP
-----------  -------------------
          3  2014-09-18 18:27:58
          2  2014-09-18 18:27:58
          1  2014-09-18 18:27:58

 BTEQ -- Enter your SQL request or BTEQ command:
INSERT INTO CARLOS.PRUEBA01(ID_N) VALUES ( 4 );


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


 BTEQ -- Enter your SQL request or BTEQ command:


SELECT *
  FROM CARLOS.PRUEBA01
;


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

       ID_N         TS_TIMESTAMP
-----------  -------------------
          3  2014-09-18 18:27:58
          4  2014-09-18 18:28:52
          2  2014-09-18 18:27:58
          1  2014-09-18 18:27:58

 BTEQ -- Enter your SQL request or BTEQ command:
SELECT *
  FROM CARLOS.PRUEBA01
 WHERE TS_TIMESTAMP > TIMESTAMP '2014-09-18 18:27:58'
;


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

       ID_N         TS_TIMESTAMP
-----------  -------------------
          4  2014-09-18 18:28:52

Todo perfecto: Las filas reciben el CURRENT_TIMESTAMP(0) para la columna TS_TIMESTAMP y la comparación en la ‘query’ final sólo devuelve una fila estrictamente mayor a TIMESTAMP ‘2014-09-18 18:27:58′ (como debe ser).

Ahora empieza lo divertido:

Vamos a crear la misma tabla sin la columna TS_TIMESTAMP. Haremos las inserciones y después añadiremos la columna TS_TIMESTAMP con el NOT NULL WITH DEFAULT (esto debe hacer que todas las filas tomen el TIMESTAMP del momento del ALTER TABLE para la nueva columna):

 BTEQ -- Enter your SQL request or BTEQ command:
DROP TABLE CARLOS.PRUEBA01;


 *** Table has been dropped.
 *** Total elapsed time was 2 seconds.


 BTEQ -- Enter your SQL request or BTEQ command:
CREATE MULTISET TABLE CARLOS.PRUEBA01
   ( ID_N INTEGER NOT NULL)
;


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


 BTEQ -- Enter your SQL request or BTEQ command:


INSERT INTO CARLOS.PRUEBA01(ID_N) VALUES ( 1 )
;INSERT INTO CARLOS.PRUEBA01(ID_N) VALUES ( 2 )
;INSERT INTO CARLOS.PRUEBA01(ID_N) VALUES ( 3 )
;


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

 *** Insert completed. One row added.

 *** Insert completed. One row added.

 BTEQ -- Enter your SQL request or BTEQ command:
ALTER TABLE CARLOS.PRUEBA01
ADD TS_TIMESTAMP TIMESTAMP(0) NOT NULL WITH DEFAULT;


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


 BTEQ -- Enter your SQL request or BTEQ command:
SELECT *
  FROM CARLOS.PRUEBA01
;


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

       ID_N         TS_TIMESTAMP
-----------  -------------------
          3  2014-09-18 18:34:03
          2  2014-09-18 18:34:03
          1  2014-09-18 18:34:03

Añadimos una fila más como marcador:

BTEQ -- Enter your SQL request or BTEQ command:
INSERT INTO CARLOS.PRUEBA01(ID_N) VALUES ( 4 );

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

BTEQ -- Enter your SQL request or BTEQ command:

SELECT *
FROM CARLOS.PRUEBA01
;

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

ID_N TS_TIMESTAMP
----------- -------------------
3 2014-09-18 18:34:03
4 2014-09-18 18:34:19
2 2014-09-18 18:34:03
1 2014-09-18 18:34:03

Si hacemos la misma comparación con el mayor estricto para el TIMESTAMP vemos que:

 BTEQ -- Enter your SQL request or BTEQ command:
SELECT *
  FROM CARLOS.PRUEBA01
 WHERE TS_TIMESTAMP > TIMESTAMP '2014-09-18 18:34:03'
;


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

       ID_N         TS_TIMESTAMP
-----------  -------------------
          3  2014-09-18 18:34:03
          4  2014-09-18 18:34:19
          2  2014-09-18 18:34:03
          1  2014-09-18 18:34:03

Y aunque hemos comparado con un mayor estricto, la ‘SELECT’ devuelve valores como si fuera un mayor o igual.

Pero esto sólo ocurre para las filas que fueron afectadas por el ALTER TABLE, ya que la fila que se insertó con la columna TS_TIMESTAMP ya incorporada, se comporta bien en las comparaciones con mayor estricto:

 BTEQ -- Enter your SQL request or BTEQ command:
SELECT *
  FROM CARLOS.PRUEBA01
WHERE TS_TIMESTAMP > TIMESTAMP '2014-09-18 18:34:19';


 *** Query completed. No rows found.
 *** Total elapsed time was 1 second.

Como los buenos prestidigitadores, mostramos la estructura de la tabla para demostrar que no hay trampa ni cartón:

 BTEQ -- Enter your SQL request or BTEQ command:
SHOW TABLE CARLOS.PRUEBA01;


 *** Text of DDL statement returned.
 *** Total elapsed time was 1 second.

-----------------------------------------------------------------------
CREATE MULTISET TABLE CARLOS.PRUEBA01 ,NO FALLBACK ,
     NO BEFORE JOURNAL,
     NO AFTER JOURNAL,
     CHECKSUM = DEFAULT,
     DEFAULT MERGEBLOCKRATIO
     (
      ID_N INTEGER NOT NULL,
      TS_TIMESTAMP TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0))
PRIMARY INDEX ( ID_N );

A mí esto me suena a ‘bug‘… y ocurre en las versiones 13.10 y 14.10 (¡!).

Así que, cuidadito con los ALTER TABLE añadiendo columnas TIMESTAMP WITH DEFAULT, te puedes llevar la misma sorpresa que me llevé yo hoy…

Saludos.

Carlos.


Seguir

Recibe cada nueva publicación en tu buzón de correo electrónico.

Únete a otros 59 seguidores