Do you think you have a problem, mr. DBA?

3 junio \03\UTC 2016

Blocked

Veinte sesiones bloqueadas (una de ellas durante casi doce horas y media), una sesión activa ejecutando una ‘query‘ de casi tres horas y tres cuartos, y tres sesiones ‘delayed‘.

Saludos.

Carlos.


‘Bug’ en CREATE TABLE AS SELECT + USI.

1 junio \01\UTC 2016

Mi compañero F. y yo hemos encontrado un comportamiento extraño en Teradata que tiene toda la pinta de ser un ‘bug‘.

Al crear una tabla con CREATE TABLE AS SELECT que además tiene un USI (Unique Secondary Index) se produce la eliminación sin errores de las filas que son duplicados de dicho USI sin que se produzca un error (sé lo que estás pensando, pero esto ocurre aunque la tabla sea MULTISET).

Pero esto sólo ocurre en determinadas circunstancias: el USI contiene una columna generada en un CASE con funciones y otra que es concatenación de varias columnas, y además es el NUPI (Non-Unique Primary Index) de la tabla.

Aquí está el caso de prueba:

 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                        14.10.06.06
LANGUAGE SUPPORT MODE          Standard
VERSION                        14.10.06.06
 

 BTEQ -- Enter your SQL request or BTEQ command:
SHOW TABLE TEST_CASE_SOURCE;

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

--------------------------------------------------------------------
CREATE MULTISET TABLE TEST_CASE_SOURCE ,
    NO FALLBACK ,
    NO BEFORE JOURNAL,
    NO AFTER JOURNAL,
    CHECKSUM = DEFAULT,
    DEFAULT MERGEBLOCKRATIO
    (
      COLUMN00 CHAR(8) CHARACTER SET LATIN NOT CASESPECIFIC NOT NULL,
      COLUMN01 CHAR(3) CHARACTER SET LATIN NOT CASESPECIFIC NOT NULL,
      COLUMN02 CHAR(4) CHARACTER SET LATIN NOT CASESPECIFIC NOT NULL,
      COLUMN03 CHAR(3) CHARACTER SET LATIN NOT CASESPECIFIC NOT NULL,
      COLUMN04 CHAR(5) CHARACTER SET LATIN NOT CASESPECIFIC NOT NULL,
      COLUMN_N VARCHAR(3) CHARACTER SET LATIN NOT CASESPECIFIC)
PRIMARY INDEX ( COLUMN04 );


 BTEQ -- Enter your SQL request or BTEQ command:
SELECT COUNT(1) FROM TEST_CASE_SOURCE;


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

  Count(1)
-----------
    3736103

Creamos la tabla nueva con las condiciones descritas arriba:

 BTEQ -- Enter your SQL request or BTEQ command:
DROP TABLE TEST_CASE_DEST ;

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


 BTEQ -- Enter your SQL request or BTEQ command:
CREATE MULTISET TABLE TEST_CASE_DEST
AS
(
  SELECT
    COLUMN00,
  (COLUMN01||COLUMN02||COLUMN03||'0'||COLUMN04) (CHAR(16)) AS COLUMNPI,
  CASE
      WHEN COLUMN_N IS NULL THEN CAST('000' AS CHAR(3))
      ELSE TD_SYSFNLIB.LPAD(TRIM(COLUMN_N),3,'0')
  END AS COLUMN_N
  FROM TEST_CASE_SOURCE
)WITH DATA
PRIMARY INDEX ( COLUMNPI )
UNIQUE INDEX(COLUMNPI, COLUMN_N, COLUMN00)
;


 *** Table has been created.
 *** Total elapsed time was 7 seconds.

Podemos ver que la tabla se ha creado sin errores, pero:

 BTEQ -- Enter your SQL request or BTEQ command:
SELECT COUNT(1) FROM TEST_CASE_DEST;


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

  Count(1)
-----------
    3735548

La tabla creada tiene menos filas que la tabla original (3735548 en vez de 3736103).

La cosa cambia si la tabla es creada como NOPI (NO Primary Index):

 BTEQ -- Enter your SQL request or BTEQ command:
DROP TABLE TEST_CASE_DEST ;


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


 BTEQ -- Enter your SQL request or BTEQ command:
CREATE MULTISET TABLE TEST_CASE_DEST
AS
(
  SELECT
    COLUMN00,
  (COLUMN01||COLUMN02||COLUMN03||'0'||COLUMN04) (CHAR(16)) AS COLUMNPI,
  CASE
      WHEN COLUMN_N IS NULL THEN CAST('000' AS CHAR(3))
      ELSE TD_SYSFNLIB.LPAD(TRIM(COLUMN_N),3,'0')
  END AS COLUMN_N
  FROM TEST_CASE_SOURCE
)WITH DATA
NO PRIMARY INDEX
UNIQUE INDEX(COLUMNPI, COLUMN_N, COLUMN00)
;

*** Failure 2803 Secondary index uniqueness violation in target table.
                Statement# 1, Info =0
 *** Total elapsed time was 6 seconds.

Aquí el error esperado sí se produce.

Pero hay más: Si creamos la misma tabla con CREATE TABLE AS SELECT … WITH NO DATA y hacemos luego el INSERT … SELECT el error aparece también:

 BTEQ -- Enter your SQL request or BTEQ command:
DROP TABLE TEST_CASE_DEST ;

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


 BTEQ -- Enter your SQL request or BTEQ command:
CREATE MULTISET TABLE TEST_CASE_DEST
AS
(
  SELECT
    COLUMN00,
  (COLUMN01||COLUMN02||COLUMN03||'0'||COLUMN04) (CHAR(16)) AS COLUMNPI,
  CASE
      WHEN COLUMN_N IS NULL THEN CAST('000' AS CHAR(3))
      ELSE TD_SYSFNLIB.LPAD(TRIM(COLUMN_N),3,'0')
  END AS COLUMN_N
  FROM TEST_CASE_SOURCE
)WITH NO DATA

Es decir, la misma SELECT utilizada en la creación de la tabla -que funcionó sin errores en el primer caso- utilizada ahora con WITH NO DATA y vuelta a utilizar en un INSERT … SELECT posterior vuelve a dar el error esperado (que es el funcionamiento correcto).

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.


Ejecutando DML’s y Stored Procedures en Oracle con TPT.

6 abril \06\UTC 2016

TPT, mediante su ODBC OPERATOR, permite acceder a otras bases de datos (Oracle, SqlServer…) generalmente para extraer datos y moverlos y normalmente para cargarlos en Teradata.

ODBC OPERATOR es un operador de tipo PRODUCER. Esto es, lee datos de una fuente (ODBC) y los escribe en un “data stream” para que los utilice un CONSUMER. Los operadores así conectados deben compartir un mismo SCHEMA. Así, se puede mediante un ODBC OPERATOR leer el contenido de una tabla Oracle y cargarlo en una tabla Teradata sin necesidad de conjugar exportaciones a fichero (o pipe) desde sqlplus con cargas posteriores a Teradata (con Fastload, Multiload, TPump, bteq o incluso TPT).

El procedimiento es bastante sencillo y se pueden encontrar muchos ejemplos descriptivos de cómo hacerlo. Hay que indicar que Teradata sólo soporta el funcionamiento de los ODBC OPERATORs utilizando los “drivers” ODBC de DataDirect. Hay quien afirma haber conseguido que funcionen usando “drivers” de Oracle o MicroSoft, pero yo sólo tuve éxito mediante los citados “drivers” de DataDirect.

Por supuesto, el primer paso es tener los “drivers” instalados y el segundo es configurar un origen de datos ODBC (en nuestro caso a Oracle 11gR2).

Una vez hecho eso se pueden efectuar pruebas de extracción de datos Oracle mediante el ODBC OPERATOR sólo para verificar conectividades y asegurarse de que todo funciona correctamente.

Pero ahora viene lo bueno: ¿Podríamos ejecutar mediante TPT sentencias DML (INSERT, UPDATE, DELETE) o incluso ejecutar procedimientos almacenados (stored procedures)?

La respuesta corta es NO. Pero la respuesta larga es SÍ.

En efecto, el ODBC OPERATOR sólo permite sentencias SELECT y, como es un PRODUCER, necesita de un CONSUMER que lea datos extraídos, por lo que, en teoría, no es posible ejecutar DMLs.

Pero la respuesta larga es SÍ. ¿Cómo? Pues haciendo un poco de trampa.

Lo que viene a continuación es un ejemplo válido sólo a efectos didácticos, y únicamente pretende mostrar cómo se podría conseguir el objetivo buscado y quizá indicar un camino de inicio para procesos más complejos.

Nos vamos a valer del hecho de que Oracle permite definir funciones con código subyacente. Si conseguimos incluir una llamada a una función de este tipo tendremos la puerta abierta para ejecutar DMLs y SPs:

Así pues si en Oracle generamos una función algo así como:

CREATE OR REPLACE FUNCTION CARLOS.PRUEBA_INSERT_3(iDesde NUMBER, 
   iHasta NUMBER)
RETURN NUMBER
AS
BEGIN
   FOR i IN iDesde..iHasta LOOP
      INSERT INTO CARLOS.PRUEBA03(ID_N, C_TXT) VALUES (i, TO_CHAR(i));
   END LOOP;
   COMMIT;
   RETURN(0);
EXCEPTION
   WHEN OTHERS THEN 
      DBMS_OUTPUT.PUT_LINE (SQLERRM);   
      RETURN(-1);
END;

Entonces, podremos definir un TPT job que llame a dicha función en un SELECT. Como necesitamos un CONSUMER para recoger el resultado del SELECT, utilizaremos un simple $FILE_WRITER que escriba el retorno de la función en un fichero:

DEFINE JOB ODBC_DML
DESCRIPTION 'ODBC DML'
(
   DEFINE SCHEMA Oracle_Return_Schema (
      id_n  VARCHAR(11)                
   );

   DEFINE OPERATOR ODBC01
   DESCRIPTION 'Teradata Parallel Transporter ODBC Operator'
   TYPE ODBC
   SCHEMA Oracle_Return_Schema
   ATTRIBUTES (
      VARCHAR DSNName = 'ORA11G_CENTOS6DD',  /* DataDirect ODBC Driver */
      VARCHAR UserName = @UserName,
      VARCHAR UserPassword = @UserPassword,
      VARCHAR TraceLevel = 'All',
      VARCHAR TruncateData = 'Yes',
      VARCHAR SelectStmt = 'SELECT CARLOS.PRUEBA_INSERT_3(1,3) FROM DUAL;'
   );

   DEFINE OPERATOR FILE_WRITER_1
   TYPE DATACONNECTOR CONSUMER
   SCHEMA * 
   ATTRIBUTES (
      VARCHAR FileName = 'Prueba_Insert_Oracle.csv',
      VARCHAR Format = 'DELIMITED',
      VARCHAR TextDelimiter=';',
      VARCHAR IndicatorMode = 'N',
      VARCHAR OpenMode = 'Write'
   );

   STEP EXPORTAR (   
      APPLY TO OPERATOR (FILE_WRITER_1)
         SELECT * FROM OPERATOR (ODBC01);
   );
   
);

Esto debería valer. Pero primero, vamos a verificar el funcionamiento de la función con sqlplus:

CARLOS@ORA11GR2> SET SERVEROUTPUT ON;
CARLOS@ORA11GR2> SELECT CARLOS.PRUEBA_INSERT_3 FROM DUAL;

PRUEBA_INSERT_3
---------------
             -1

ORA-14551: cannot perform a DML operation inside a query
CARLOS@ORA11GR2>     

¡Es verdad! ¡No podemos hacer DML en un SELECT! Pero lo podemos soslayar utilizando una transacción autónoma (PRAGMA AUTONOMOUS_TRANSACTION). Esto, por supuesto, tiene importantes efectos en el proceso, pero nos permite hacer:

CARLOS@ORA11GR2> ed
Wrote file afiedt.buf

  1  CREATE OR REPLACE FUNCTION CARLOS.PRUEBA_INSERT_3(iDesde NUMBER, 
  2     iHasta NUMBER)
  3  RETURN NUMBER
  4  AS
  5  PRAGMA AUTONOMOUS_TRANSACTION;
  6  BEGIN
  7   FOR i IN iDesde..iHasta LOOP
  8      INSERT INTO CARLOS.PRUEBA03(ID_N, C_TXT) VALUES (i, TO_CHAR(i));
  9   END LOOP;
 10   COMMIT;
 11   RETURN(0);
 12  EXCEPTION
 13   WHEN OTHERS THEN
 14      RETURN(-1);
 15* END;
CARLOS@ORA11GR2> /

Function created.

CARLOS@ORA11GR2> SELECT PRUEBA_INSERT_3(1, 3) FROM DUAL;

PRUEBA_INSERT_3(1,3)
--------------------
                   0

CARLOS@ORA11GR2> SELECT * FROM PRUEBA03;

      ID_N C_TXT
---------- ----------
         1 1
         2 2
         3 3

CARLOS@ORA11GR2> 

Y ahora podemos ejecutar el TPT (sustituyendo ‘SELECT CARLOS.PRUEBA_INSERT_3(1,3) FROM DUAL;’ por ‘SELECT CARLOS.PRUEBA_INSERT_3(4,6) FROM DUAL;’ ):

C:\Carlos\TPT\TPT_Oracle_To_Teradata>tbuild -f Prueba_Insert_Oracle.tpt
Teradata Parallel Transporter Version 14.10.00.11 64-Bit
Job log: C:\Program Files\Teradata\client\14.10\Teradata Parallel Transporter/logs/carlos-57.out
Job id is carlos-57, running on CARLOS03
Teradata Parallel Transporter DataConnector Operator Version 14.10.00.11
FILE_WRITER_1: Instance 1 directing private log report to 'dtacop-carlos-6672-1'.
FILE_WRITER_1: DataConnector Consumer operator Instances: 1
Teradata Parallel Transporter ODBC Operator Version 14.10.00.11
ODBC01: private log not specified
FILE_WRITER_1: ECI operator ID: 'FILE_WRITER_1-6672'
FILE_WRITER_1: Operator instance 1 processing file 'C:\Trazas\Prueba_Insert_Oracle.csv'.
ODBC01: connecting sessions
ODBC01: sending SELECT request
ODBC01: data retrieval complete
ODBC01: Total Rows Exported:  1
FILE_WRITER_1: Total files processed: 1.
ODBC01: disconnecting sessions
ODBC01: Total processor time used = '0.0312002 Second(s)'
ODBC01: Start : Wed Apr 06 19:26:48 2016
ODBC01: End   : Wed Apr 06 19:26:53 2016
Job step EXPORTAR completed successfully
Job carlos completed successfully
Job start: Wed Apr 06 19:26:45 2016
Job end:   Wed Apr 06 19:26:53 2016

C:\Carlos\TPT\TPT_Oracle_To_Teradata>

Y en Oracle:

CARLOS@ORA11GR2> SELECT * FROM PRUEBA03;

      ID_N C_TXT
---------- ----------
         1 1
         2 2
         3 3
         4 4
         5 5
         6 6

6 rows selected.

CARLOS@ORA11GR2> 

Con lo que hemos conseguido ejecutar un INSERT en Oracle… haciendo trampas, claro.

Pero lo bueno es que podríamos ejecutar un procedimiento almacenado también. Basta con crear una función envoltorio que llame a un procedimiento almacenado:

CREATE OR REPLACE PROCEDURE CARLOS.PRUEBA_INSERT_4_SP( iDesde NUMBER, 
   iHasta NUMBER)
AS
BEGIN
   FOR i IN iDesde..iHasta LOOP
      INSERT INTO CARLOS.PRUEBA03(ID_N, C_TXT) VALUES (i, TO_CHAR(i));
   END LOOP;
END;


CREATE OR REPLACE FUNCTION CARLOS.PRUEBA_INSERT_4_FN( iDesde NUMBER, 
   iHasta NUMBER)
RETURN NUMBER
AS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
   CARLOS.PRUEBA_INSERT_4_SP( iDesde, iHasta);
   COMMIT;
   RETURN(0);
EXCEPTION
   WHEN OTHERS THEN 
      DBMS_OUTPUT.PUT_LINE (SQLERRM);
      RETURN(-1);
END;

Si modificamos el “SelectStmt” del script de TPT:

VARCHAR SelectStmt = 'SELECT CARLOS.PRUEBA_INSERT_4_FN (7,20) FROM DUAL;'

Y ejecutamos:

C:\Carlos\TPT\TPT_Oracle_To_Teradata>tbuild -f Prueba_Insert_Oracle.tpt
Teradata Parallel Transporter Version 14.10.00.11 64-Bit
Job log: C:\Program Files\Teradata\client\14.10\Teradata Parallel Transporter/logs/carlos-58.out
Job id is carlos-58, running on CARLOS03
Teradata Parallel Transporter DataConnector Operator Version 14.10.00.11
FILE_WRITER_1: Instance 1 directing private log report to 'dtacop-carlos-6892-1'.
FILE_WRITER_1: DataConnector Consumer operator Instances: 1
Teradata Parallel Transporter ODBC Operator Version 14.10.00.11
ODBC01: private log not specified
FILE_WRITER_1: ECI operator ID: 'FILE_WRITER_1-6892'
FILE_WRITER_1: Operator instance 1 processing file 'C:\Trazas\Prueba_Insert_Oracle.csv'.
ODBC01: connecting sessions
ODBC01: sending SELECT request
ODBC01: data retrieval complete
ODBC01: Total Rows Exported:  1
FILE_WRITER_1: Total files processed: 1.
ODBC01: disconnecting sessions
ODBC01: Total processor time used = '0.0156001 Second(s)'
ODBC01: Start : Wed Apr 06 19:45:35 2016
ODBC01: End   : Wed Apr 06 19:45:41 2016
Job step EXPORTAR completed successfully
Job carlos completed successfully
Job start: Wed Apr 06 19:45:31 2016
Job end:   Wed Apr 06 19:45:41 2016

C:\Carlos\TPT\TPT_Oracle_To_Teradata>

Vemos que:

CARLOS@ORA11GR2> SELECT * FROM PRUEBA03;

      ID_N C_TXT
---------- ----------
         1 1
         2 2
         3 3
         4 4
         5 5
         6 6
         7 7
         8 8
         9 9
        10 10
        11 11
        12 12
        13 13
        14 14
        15 15
        16 16
        17 17
        18 18
        19 19
        20 20

20 rows selected.

CARLOS@ORA11GR2> 

Y así podríamos ejecutar DMLs y stored procedures usando TPT.

The rest is up to you…

Saludos.

Carlos.


Raíz n-ésima en Teradata.

9 marzo \09\UTC 2016

En los cálculos habituales a los que estamos acostumbrados normalmente sólo se necesitan raíces cuadradas (y eso si alguna vez se necesitan). Para ello existe la función SQRT (square root) que es simple y sencilla.

SELECT SQRT(16);


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

              Sqrt(16)
----------------------
 4.00000000000000E 000

¿Pero qué pasa si queremos una raíz cúbica, cuarta o cualquier otra raíz n-ésima? Pues que entonces tenemos que buscar otro método.
Lo normal es utilizar una exponenciación inversa: SQRT(16)=16**(1/2)

Así que:

BTEQ -- Enter your SQL request or BTEQ command:
SELECT 243**(1/5);


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

          (243**(1/5))
----------------------
 1.00000000000000E 000

¿1? ¿Cómo es posible? Pues claro, porque 1/5 trunca a 0, y n**0 = 1.

Entonces hay que hacer un CAST para evitarlo:

SELECT 243**(1(FLOAT)/5);
                       $
 *** Failure 3706 Syntax error: expected something between ')' and '/'.
                Statement# 1, Info =24
 *** Total elapsed time was 1 second.

Con un CAST ‘TERADATA’ casca. Hay que hacer un CAST ‘ANSI’.

 BTEQ -- Enter your SQL request or BTEQ command:
SELECT 243**CAST(1 AS FLOAT)/5;


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

          ((243**1)/5)
----------------------
 4.86000000000000E 001

Esto tampoco es lo que esperábamos. Pero aquí el problema está en la precedencia de operadores: se ejecuta primero 243**1 y luego se divide por 5.
Es decir: (243**1)/5 = 48.6

Así que lo mejor será utilizar los paréntesis con criterio:

 BTEQ -- Enter your SQL request or BTEQ command:
SELECT 243**(CAST(1 AS FLOAT)/5);


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

          (243**(1/5))
----------------------
 3.00000000000000E 000

Y esto sí es lo que esperábamos.

Por último, aunque podemos utilizar la potenciación con números negativos:

SELECT (-2)**(3);


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

               (-2**3)
----------------------
-8.00000000000000E 000

 BTEQ -- Enter your SQL request or BTEQ command:
SELECT (-8)**(3);


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

               (-8**3)
----------------------
-5.12000000000000E 002

No podemos hacer lo mismo con exponentes menores que 1:

 BTEQ -- Enter your SQL request or BTEQ command:
SELECT (-8)**(CAST(1 AS FLOAT)/3);

 *** Failure 2622 Bad argument for ** operator.
                Statement# 1, Info =0
 *** Total elapsed time was 1 second.

También hay que tener en cuenta que estamos trabajando con FLOATs de doble precisión y sus posibles errores inherentes.

Saludos.

Carlos.


Cadenas a filas con STRTOK_SPLIT_TO_TABLE()

18 enero \18\UTC 2016

La conversión de columnas de cadenas de datos separados por un ‘token’ en filas se ha tratado ya en este blog. Entonces se hizo utilizando recursividad (WITH RECURSIVE…), en esta ocasión vamos a ver cómo se puede hacer con la nueva función STRTOK_SPLIT_TO_TABLE() -disponible en Teradata desde la versión 14.0- y algunas particularidades que tiene su uso.

La función hace lo que dice su nombre: convierte en tablas cadenas delimitadas por ‘tokens’. Pero hace falta saber un poco más de ella: lo principal es que necesita un valor ‘key’ para referenciar las filas de la tabla resultante de la cadena de ‘tokens’ a partir de las filas de la tabla original. Esta clave funciona como una especie de PK/FK.

Las columnas de cadenas separadas por ‘tokens’ pueden ser tanto caracteres “normales” (CHAR, VARCHAR) como CLOB. La cadena ‘token’ (delimitador) puede ser de hasta 64 caracteres (el tipo es VARCHAR(64)), aunque normalmente será un único caracter (de hecho, su “DEFAULT” es el espacio simple (‘ ‘)).

Bueno, pues con todo esto vamos a crearnos la consabida tabla de ejemplos:

 BTEQ -- Enter your SQL request or BTEQ command:
CREATE MULTISET TABLE CARLOS.PRUEBA_08 ,
     NO FALLBACK ,
     NO BEFORE JOURNAL,
     NO AFTER JOURNAL,
     CHECKSUM = DEFAULT,
     DEFAULT MERGEBLOCKRATIO
     (
      ID_N INTEGER NOT NULL,
      C_TXT CHAR(1) NOT NULL,
      V_TXT VARCHAR(25) 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.PRUEBA_08 VALUES (1,'A','001');


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


 BTEQ -- Enter your SQL request or BTEQ command:
INSERT INTO CARLOS.PRUEBA_08 VALUES (2,'B','002,003');


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


 BTEQ -- Enter your SQL request or BTEQ command:
INSERT INTO CARLOS.PRUEBA_08 VALUES (4,'D','004,005,006');


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


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


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

       ID_N  C_TXT  V_TXT
-----------  -----  -------------------------
          1  A      001
          2  B      002,003
          4  D      004,005,006

Basándonos en la definición y ejemplos de la documentación Teradata, podemos hacer:

 BTEQ -- Enter your SQL request or BTEQ command:
SELECT d.*
FROM TABLE (TD_SYSFNLIB.STRTOK_SPLIT_TO_TABLE(CARLOS.PRUEBA_08.ID_N, 
                                              CARLOS.PRUEBA_08.V_TXT, 
                                              ',')
            RETURNS (outkey integer, 
                     tokennum integer, 
                     token varchar(20) character set unicode) 
           ) as d 
ORDER BY 1,2;


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

     outkey     tokennum  token
-----------  -----------  --------------------
          1            1  001
          2            1  002
          2            2  003
          4            1  004
          4            2  005
          4            3  006

¿Qué hemos tenido en cuenta? Pues que hemos elegido la columna ID_N como nuestra clave y la coma ‘,’ como nuestro separador. Fácil.

Pero supongamos que quermos ir un poco más allá: que queremos conservar en lo posible la estructura de la tabla original, con sus tres columnas (aunque podrían ser más). Entonces tenemos que recurrir a una versión un poco más complicada. Lo primero es que la clave elegida (inkey/outkey) puede ser VARCHAR y, aunque la documentación dice que puede ser de hasta 10 caracteres (VARCHAR(10)), lo cierto es que admite hasta VARCHAR(32). Otra cosa muy importante a tener en cuenta es que esta clave VARCHAR debe ser CHARACTER SET UNICODE (al igual que el valor de cada parte de la cadena devuelta).

Sabiendo esto, podemos concatenar las columnas que necesitemos (hasta un límite de VARCHAR(32)):

          
SELECT d.*
FROM TABLE (TD_SYSFNLIB.STRTOK_SPLIT_TO_TABLE(
               CAST(CARLOS.PRUEBA_08.ID_N AS CHAR(1)) || '|' ||
                  CARLOS.PRUEBA_08.C_TXT, 
               CARLOS.PRUEBA_08.V_TXT, 
               ',')
            RETURNS (
               outkey VARCHAR(32) character set unicode, 
               tokennum integer, 
               token varchar(20) character set unicode)
           ) d 
ORDER BY 1,2;


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

outkey         tokennum  token
----------  -----------  --------------------
1|A                   1  001
2|B                   1  002
2|B                   2  003
4|D                   1  004
4|D                   2  005
4|D                   3  006

OK. Entonces podemos volver a descomponer la clave en sus columnas originales utilizando la función STRTOK(), que es prima hermana de nuestra STRTOK_SPLIT_TO_TABLE(), y utilizar simples CAST para que el resultado sea lo más parecido a nuestra tabla original:

 BTEQ -- Enter your SQL request or BTEQ command:
SELECT CAST(TD_SYSFNLIB.STRTOK(d.OUTKEY,'|',1) AS INTEGER) ID_N,
       CAST(TD_SYSFNLIB.STRTOK(d.OUTKEY,'|',2) AS CHAR(1)) C_TXT,
       CAST(d.TOKEN AS VARCHAR(20)) V_TXT_PART
FROM TABLE (TD_SYSFNLIB.STRTOK_SPLIT_TO_TABLE(
               CAST(CARLOS.PRUEBA_08.ID_N AS CHAR(1)) || '|' ||
                  CARLOS.PRUEBA_08.C_TXT, 
               CARLOS.PRUEBA_08.V_TXT, 
               ',')
            RETURNS (OUTKEY VARCHAR(32) CHARACTER SET UNICODE, 
                     TOKENNUM INTEGER, 
                     TOKEN VARCHAR(20) CHARACTER SET UNICODE)
           ) d 
ORDER BY 1,2,3;


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

       ID_N  C_TXT  V_TXT_PART
-----------  -----  -------------------------
          1  A      001
          2  B      002
          2  B      003
          4  D      004
          4  D      005
          4  D      006

Saludos.

Carlos.


17.000.000.000 filas

16 diciembre \16\UTC 2015

O sea, diecisiete mil millones de filas, es lo que hemos estado moviendo hoy en la base de datos Teradata.

Saludos.

Carlos.