Capítulo 1: Arquitectura de Teradata
1.1 ¿Qué es Teradata?
Teradata es un sistema de gestión de bases de datos relacionales (RDBMS) diseñado específicamente para el procesamiento analítico de grandes volúmenes de datos (OLAP). Su arquitectura de procesamiento masivamente paralelo (MPP) lo distingue de otros motores de bases de datos y lo convierte en la solución preferida para entornos de Data Warehouse empresariales.
1.2 Componentes Principales
| Componente | Descripción | Rol |
|---|---|---|
| Parsing Engine (PE) | Motor de análisis sintáctico y semántico | Recibe las consultas SQL del usuario |
| BYNET | Red de interconexión interna de alta velocidad | Comunica PEs con AMPs |
| Access Module Processor (AMP) | Procesador de acceso a datos | Lee/escribe datos en los discos |
| Virtual Disk (vDisk) | Almacenamiento virtual asignado a cada AMP | Almacena los datos físicamente |
| Clique | Grupo de nodos que comparten discos | Garantiza alta disponibilidad |
1.3 Distribución de Datos: Hash de Fila
Teradata distribuye las filas entre los AMPs usando un algoritmo de hash aplicado al Índice Primario (PI) de cada tabla. Este mecanismo es fundamental para entender la performance del sistema.
Flujo de distribución de una fila INSERT
- Usuario ejecuta: INSERT INTO ventas VALUES (101, ‘Buenos Aires’, 50000)
- El PE calcula: HASH( valor_PI = 101 ) → 0xA3F7…
- El BYNET enruta: hash % N_AMPs → AMP #7
- AMP #7 escribe: la fila en su vDisk
1.4 Índice Primario (PI)
El Índice Primario es la columna o combinación de columnas que determina en qué AMP se almacenará cada fila. Su elección es la decisión de diseño más importante en Teradata.
| Tipo de PI | Característica | Cuándo Usarlo |
|---|---|---|
| UPI – Unique Primary Index | Valores únicos por fila | Claves de negocio únicas (ID de cliente) |
| NUPI – Non-Unique PI | Permite duplicados | Columnas de alta cardinalidad, pero no únicas |
| No PI (NoPI Table) | Sin índice primario | Tablas de staging para carga masiva |
Un PI mal elegido puede concentrar datos en pocos AMPs (skew), degradando drásticamente el rendimiento. Verificar siempre con HELP STATS y las vistas de sistema.
Capítulo 2: Tipos de Datos en Teradata
2.1 Tipos de Datos Numéricos
| Tipo | Tamaño | Rango / Precisión | Uso Típico |
|---|---|---|---|
| BYTEINT | 1 byte | -128 a 127 | Flags, indicadores pequeños |
| SMALLINT | 2 bytes | -32,768 a 32,767 | Códigos de estado |
| INTEGER / INT | 4 bytes | -2.1B a 2.1B | IDs, contadores |
| BIGINT | 8 bytes | ±9.2 × 10^18 | IDs de transacciones masivas |
| DECIMAL(p,s) | variable | Hasta 38 dígitos | Montos, precios (evita redondeo) |
| FLOAT / REAL | 8 bytes | IEEE 754 doble precisión | Cálculos científicos |
| NUMBER(p,s) | variable | Compatibilidad ANSI | Migración desde Oracle |
2.2 Tipos de Datos de Caracteres
| Tipo | Descripción | Máximo | Notas |
|---|---|---|---|
| CHAR(n) | Longitud fija, rellena con espacios | 64,000 bytes | Para códigos de longitud fija |
| VARCHAR(n) | Longitud variable | 64,000 bytes | Texto de longitud variable |
| CLOB | Character Large Object | 2 GB | Documentos, texto largo |
| CHAR VARYING | Sinónimo de VARCHAR | - | Compatibilidad ANSI |
2.3 Tipos de Datos de Fecha y Hora
| Tipo | Formato Interno | Ejemplo | Notas |
|---|---|---|---|
| DATE | INTEGER (YYYYMMDD) | 2025-06-15 | Almacenado como entero, muy eficiente |
| TIME | HH:MI:SS.nnnnnn | 14:30:00.000000 | Con o sin zona horaria |
| TIMESTAMP | DATE + TIME | 2025-06-15 14:30:00 | Estándar para auditorías |
| INTERVAL | Duración relativa | INTERVAL ‘3’ MONTH | Para cálculos de períodos |
Ejemplo: Creación de tabla con tipos de datos correctos
CREATE TABLE clientes (
cliente_id INTEGER NOT NULL,
nombre VARCHAR(100) NOT NULL,
email VARCHAR(200),
fecha_alta DATE FORMAT 'YYYY-MM-DD',
saldo DECIMAL(15,2) DEFAULT 0,
activo BYTEINT DEFAULT 1
)
PRIMARY INDEX (cliente_id);
Capítulo 3: SQL en Teradata – Comandos Esenciales
3.1 DDL – Data Definition Language
Los comandos DDL permiten crear, modificar y eliminar objetos de la base de datos.
CREATE TABLE – Sintaxis completa con opciones Teradata
CREATE [SET | MULTISET] TABLE nombre_tabla,
[NO] FALLBACK [PROTECTION],
[NO] BEFORE JOURNAL,
[NO] AFTER JOURNAL,
CHECKSUM = DEFAULT
(
col1 INTEGER NOT NULL,
col2 VARCHAR(100),
col3 DATE FORMAT 'YYYY-MM-DD'
)
PRIMARY INDEX nombre_pi (col1)
[PARTITION BY RANGE_N(col3 BETWEEN DATE '2020-01-01' AND DATE '2025-12-31' EACH INTERVAL '1' MONTH)];
| Opción | SET | MULTISET |
|---|---|---|
| Filas duplicadas | No permite (descarta silenciosamente) | Permite duplicados |
| Comportamiento PI | Único por defecto lógico | No unicidad implícita |
| Uso recomendado | Tablas de hechos con PI único | Staging, tablas temporales |
3.2 DML – Data Manipulation Language
INSERT / INSERT-SELECT
-- Inserción simple
INSERT INTO ventas (venta_id, cliente_id, monto, fecha)
VALUES (1001, 500, 15000.00, DATE '2025-06-15');
-- Insert-Select (ETL típico)
INSERT INTO ventas_historico
SELECT * FROM ventas_staging
WHERE fecha < DATE - 365;
UPDATE y DELETE con condiciones
-- UPDATE con subconsulta correlacionada
UPDATE v
FROM ventas v
SET monto = monto * 1.10
WHERE EXISTS (
SELECT 1 FROM clientes_premium cp
WHERE cp.cliente_id = v.cliente_id
);
-- DELETE con JOIN
DELETE FROM ventas_staging
WHERE (venta_id, fecha) IN (
SELECT venta_id, fecha FROM ventas
);
3.3 Opciones UPSERT: MERGE INTO
MERGE INTO – Insertar o actualizar en una sola instrucción
MERGE INTO clientes AS tgt
USING clientes_staging AS src
ON tgt.cliente_id = src.cliente_id
WHEN MATCHED THEN
UPDATE SET nombre = src.nombre,
email = src.email
WHEN NOT MATCHED THEN
INSERT (cliente_id, nombre, email)
VALUES (src.cliente_id, src.nombre, src.email);
Capítulo 4: Funciones Analíticas (OLAP)
Las funciones analíticas (también llamadas funciones de ventana) son una de las características más poderosas y diferenciales de Teradata. Permiten calcular valores acumulados, rankings, comparaciones con filas anteriores y posteriores, todo dentro de una sola consulta SQL sin subconsultas complejas.
4.1 Sintaxis General de Funciones de Ventana
Estructura OVER() – clave de las funciones analíticas FUNCIÓN_ANALÍTICA(columna)
OVER ( [PARTITION BY col_agrupación] [ORDER BY col_orden ASC|DESC] [ROWS|RANGE BETWEEN inicio AND fin] )
4.2 Funciones de Ranking
| Función | Descripción | Manejo de Empates |
|---|---|---|
| RANK() | Ranking con saltos ante empate | Empates comparten rango, el siguiente salta |
| DENSE_RANK() | Ranking sin saltos | Empates comparten rango, el siguiente es consecutivo |
| ROW_NUMBER() | Número de fila único | Sin empates, siempre único |
| PERCENT_RANK() | Percentil del ranking | (rank-1)/(total_filas-1) |
| CUME_DIST() | Distribución acumulada | Fracción de filas <= valor actual |
| NTILE(n) | Divide en N grupos iguales | Distribuye uniformemente |
Ejemplo práctico – Ranking de ventas por región
SELECT
region,
vendedor,
monto_venta,
RANK() OVER (PARTITION BY region ORDER BY monto_venta DESC) AS rank_region,
DENSE_RANK() OVER (PARTITION BY region ORDER BY monto_venta DESC) AS drank_region,
ROW_NUMBER() OVER (PARTITION BY region ORDER BY monto_venta DESC) AS nro_fila,
NTILE(4) OVER (PARTITION BY region ORDER BY monto_venta DESC) AS cuartil
FROM ventas
WHERE fecha BETWEEN DATE '2025-01-01' AND DATE '2025-12-31';
RANK y DENSE_RANK son iguales salvo en los saltos. Usar DENSE_RANK cuando se quiera numerar posiciones consecutivas (ej: podio 1°, 2°, 3°). Usar RANK cuando se quiera respetar el salto (ej: dos 2°, entonces el siguiente es 4°).
4.3 Funciones de Ventana Deslizante
SUM, AVG, MIN, MAX con ROWS BETWEEN
SELECT
fecha,
monto,
-- Suma acumulada desde el inicio hasta la fila actual
SUM(monto) OVER (ORDER BY fecha ROWS UNBOUNDED PRECEDING) AS suma_acum,
-- Media móvil de los últimos 7 días
AVG(monto) OVER (ORDER BY fecha ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS media_7d,
-- Máximo en ventana de 30 días
MAX(monto) OVER (ORDER BY fecha ROWS BETWEEN 29 PRECEDING AND CURRENT ROW) AS max_30d
FROM ventas_diarias
ORDER BY fecha;
4.4 Funciones LAG y LEAD – Comparar con Filas Adyacentes
LAG/LEAD – Variación respecto al período anterior/siguiente
SELECT
fecha,
monto,
-- Valor del período anterior (offset=1, default=0 si no existe)
LAG(monto, 1, 0) OVER (ORDER BY fecha) AS monto_anterior,
-- Valor del período siguiente
LEAD(monto, 1, 0) OVER (ORDER BY fecha) AS monto_siguiente,
-- Variación porcentual respecto al mes anterior
CAST((monto - LAG(monto,1) OVER (ORDER BY fecha))/ NULLIFZERO(LAG(monto,1) OVER (ORDER BY fecha))* 100 AS DECIMAL(10,2)) AS var_pct
FROM ventas_mensuales
ORDER BY fecha;
4.5 FIRST_VALUE y LAST_VALUE
Obtener el primer o último valor de una partición
SELECT
cliente_id,
fecha_compra,
monto,
-- Primera compra del cliente
FIRST_VALUE(fecha_compra) OVER (
PARTITION BY cliente_id
ORDER BY fecha_compra
ROWS UNBOUNDED PRECEDING
) AS primera_compra,
-- Monto de la última compra
LAST_VALUE(monto) OVER (
PARTITION BY cliente_id
ORDER BY fecha_compra
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS ultima_compra_monto
FROM compras
ORDER BY cliente_id, fecha_compra;
Capítulo 5: Funciones de Fecha y Hora
5.1 Operaciones con DATE
| Función / Expresión | Ejemplo | Resultado |
|---|---|---|
| DATE (fecha actual) | SELECT DATE | 2025-06-15 |
| DATE + n | DATE + 30 | Fecha + 30 días |
| DATE - DATE | DATE ‘2025-12-31’ - DATE ‘2025-01-01’ | 364 (días entre fechas) |
| EXTRACT(parte FROM fecha) | EXTRACT(YEAR FROM DATE ‘2025-06-15’) | 2025 |
| ADD_MONTHS(fecha, n) | ADD_MONTHS(DATE, 3) | Suma 3 meses exactos |
| LAST_DAY(fecha) | LAST_DAY(DATE ‘2025-06-01’) | 2025-06-30 |
| NEXT_DAY(fecha, dia) | NEXT_DAY(DATE, ‘Monday’) | Próximo lunes |
| FORMAT fecha (YYYY-MM-DD) | (DATE)(FORMAT ‘YYYY-MM-DD’) | Convierte a string formateado |
Ejemplo completo – Análisis temporal de ventas
SELECT
EXTRACT(YEAR FROM fecha) AS anio,
EXTRACT(MONTH FROM fecha) AS mes,
EXTRACT(DAY FROM fecha) AS dia,
-- Día de la semana (1=Domingo ... 7=Sábado en TD)
TD_DAY_OF_WEEK(fecha) AS dia_semana,
-- Inicio y fin del mes
fecha - (EXTRACT(DAY FROM fecha) - 1) AS primer_dia_mes,
LAST_DAY(fecha) AS ultimo_dia_mes,
-- Diferencia en meses con respecto a hoy
MONTHS_BETWEEN(DATE, fecha) AS meses_transcurridos,
COUNT(*) AS total_ventas,
SUM(monto) AS total_monto
FROM ventas
GROUP BY 1,2,3,4,5,6,7
ORDER BY 1,2,3;
5.2 TIMESTAMP y Zonas Horarias
Manejo de TIMESTAMP con zona horaria
-- TIMESTAMP actual del sistema
SELECT CURRENT_TIMESTAMP;
SELECT CURRENT_TIMESTAMP(6); -- Con microsegundos
-- Conversión entre zonas horarias
SELECT CURRENT_TIMESTAMP AT 'America/Buenos_Aires';
SELECT CURRENT_TIMESTAMP AT TIME ZONE INTERVAL '-03:00' HOUR TO MINUTE;
-- Diferencia entre TIMESTAMPS (resultado en días)
SELECT CAST(
(CAST('2025-12-31 23:59:59' AS TIMESTAMP) - CAST('2025-01-01 00:00:00' AS TIMESTAMP)
) SECOND(4) AS DECIMAL(15,2)) / 86400 AS dias_diferencia;
5.3 Funciones TD_SYSFNLIB Específicas de Teradata
| Función | Descripción | Ejemplo de Uso |
|---|---|---|
| TD_DAY_OF_WEEK(d) | Día de la semana (0=Dom) | TD_DAY_OF_WEEK(fecha) = 0 (Domingo) |
| TD_DAY_OF_YEAR(d) | Día del año (1-366) | TD_DAY_OF_YEAR(DATE ‘2025-06-15’) = 166 |
| TD_WEEK_OF_YEAR(d) | Semana ISO del año | Para análisis semanal |
| TD_FISCAL_QUARTER(d,m) | Trimestre fiscal | TD_FISCAL_QUARTER(fecha, 4) – año fiscal desde abril |
| TD_SYSDATETIME() | Fecha-hora con precisión de ns | Para logs de auditoría |
Capítulo 6: Funciones de String y Conversión
6.1 Funciones de Manipulación de Texto
| Función | Sintaxis | Ejemplo |
|---|---|---|
| TRIM | TRIM([LEADING|TRAILING|BOTH] char FROM expr) | TRIM(BOTH ’ ’ FROM ’ Hola ’) → ‘Hola’ |
| SUBSTR / SUBSTRING | SUBSTR(str, inicio, largo) | SUBSTR(‘Teradata’,1,4) → ‘Tera’ |
| CHARACTERS / CHAR_LENGTH | CHARACTERS(str) | CHARACTERS(‘hola’) → 4 |
| INDEX / INSTR | INDEX(str, buscar) | INDEX(‘Buenos Aires’,‘Aires’) → 8 |
| UPPER / LOWER | UPPER(str) | UPPER(‘teradata’) → ‘TERADATA’ |
| CONCAT / || | str1 || str2 | ’Tera’ || ‘data’ → ‘Teradata’ |
| TRANSLATE | TRANSLATE(str USING LATIN_TO_UNICODE) | Conversión de juego de caracteres |
| REGEXP_SUBSTR | REGEXP_SUBSTR(str, patrón) | Extrae con expresión regular |
| REGEXP_REPLACE | REGEXP_REPLACE(str, pat, reemplazo) | Reemplaza con regex |
| STRTOK | STRTOK(str, delim, n) | STRTOK(‘a;b;c’,’;‘,2) → ‘b’ |
| OREPLACE | OREPLACE(str, buscar, reempl) | OREPLACE(‘Hola Mundo’,‘Mundo’,‘TD’) → ‘Hola TD’ |
Ejemplo práctico – Limpieza y parsing de datos de texto
SELECT
-- Normalizar nombre
TRIM(UPPER(nombre)) AS nombre_limpio,
-- Extraer dominio del email
SUBSTR(email, INDEX(email,'@') + 1) AS dominio,
-- Separar código de área de teléfono '(011) 4567-8901'
STRTOK(OREPLACE(OREPLACE(telefono,'(',''),')',''), ' ', 1) AS cod_area,
-- Enmascarar email
SUBSTR(email,1,3) || '***' ||
SUBSTR(email, INDEX(email,'@')) AS email_mascara,
-- Validar formato con regex
CASE WHEN REGEXP_SIMILAR(email,'^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\[A-Za-z]{2,}$') = 1
THEN 'VÁLIDO' ELSE 'INVÁLIDO' END AS email_estado
FROM clientes;
6.2 Conversiones de Tipo: CAST y TO_CHAR / TO_DATE
CAST – conversión explícita de tipos
-- Numérico a texto
CAST(12345.67 AS VARCHAR(20)) → '12345.67'
-- Texto a fecha
CAST('2025-06-15' AS DATE FORMAT 'YYYY-MM-DD')
-- Texto a número
CAST('9876.54' AS DECIMAL(10,2)) → 9876.54
-- TIMESTAMP a DATE
CAST(CURRENT_TIMESTAMP AS DATE) → fecha actual
-- Con formato de salida
CAST(monto AS VARCHAR(20)) (FORMAT '$$$$,$$$,$$9.99')
Teradata acepta tanto CAST (ANSI estándar) como la sintaxis abreviada: (columna)(TIPO). Ejemplo: (fecha)(FORMAT ‘YYYYMMDD’). En ambientes mixtos preferir CAST para portabilidad.
Capítulo 7: Optimización y Performance
7.1 EXPLAIN – El Plan de Ejecución
El comando EXPLAIN muestra cómo el Optimizador de Teradata planifica ejecutar una consulta. Es la herramienta más importante para diagnosticar y optimizar consultas.
Cómo interpretar EXPLAIN
EXPLAIN
SELECT v.cliente_id, SUM(v.monto)
FROM ventas v
JOIN clientes c ON v.cliente_id = c.cliente_id
WHERE v.fecha >= DATE - 30
GROUP BY 1;
-- Términos clave en la salida del EXPLAIN:
-- 'We do an all-AMPs retrieve' → Full table scan (costoso)
-- 'We do a single-AMP retrieve' → Acceso por PI (ideal)
-- 'Redistributing rows' → Redistribución de datos entre AMPs
-- 'Duplicating rows' → Duplicación para JOIN (ineficiente)
-- 'Spool' → Archivo temporal en disco
-- 'Confidence level' → Confianza en estadísticas (high=bueno)
7.2 Estadísticas – COLLECT STATISTICS
El Optimizador de Teradata depende de estadísticas actualizadas para generar buenos planes de ejecución. Sin estadísticas, el Optimizador usa estimaciones que pueden ser muy incorrectas.
Comandos de estadísticas
-- Recolectar estadísticas de una columna individual
COLLECT STATISTICS ON ventas COLUMN (cliente_id);
-- Estadísticas de índice primario
COLLECT STATISTICS ON ventas INDEX (venta_id);
-- Estadísticas de múltiples columnas (histograma conjunto)
COLLECT STATISTICS ON ventas COLUMN (fecha, region);
-- Ver estadísticas existentes
HELP STATS ventas;
-- Ver detalle de una estadística
SHOW STATS VALUES ON ventas;
-- Actualizar solo si han cambiado > umbral (recomendado en producción)
COLLECT STATISTICS USING THRESHOLD 10 PERCENT ON ventas COLUMN (fecha);
7.3 Estrategias para Evitar el Skew
| Problema | Causa | Solución |
|---|---|---|
| Skew de PI | PI con baja cardinalidad o nulos | Elegir un PI con alta cardinalidad y sin nulos |
| Producto Cartesiano | JOIN sin condición o con constante | Verificar ON clause, usar EXPLAIN |
| Spool excesivo | SELECT * sin filtros en tablas grandes | Aplicar filtros tempranos, usar vistas |
| Redistribución en JOINs | Columnas de JOIN distintas al PI | Crear PI/SI en columnas de JOIN frecuentes |
| Lock contention | Muchas transacciones en las mismas filas | Usar ACCESS LOCK en consultas analíticas |
7.4 Índices Secundarios y PPI
Tipos de índices adicionales
-- Índice Secundario Único (USI) – como un UPI alternativo
CREATE UNIQUE INDEX (email) ON clientes;
-- Índice Secundario No Único (NUSI) – para columnas de filtro frecuente
CREATE INDEX (region, fecha) ON ventas;
-- Partition Primary Index (PPI) – particionamiento por rango de fecha
CREATE TABLE ventas_ppi (
venta_id INTEGER NOT NULL,
fecha DATE NOT NULL,
monto DECIMAL(15,2)
)
PRIMARY INDEX (venta_id)
PARTITION BY RANGE_N(
fecha BETWEEN DATE '2020-01-01' AND DATE '2025-12-31'
EACH INTERVAL '1' MONTH
);
ℹ Info: Con PPI, Teradata puede eliminar particiones completas al aplicar filtros de fecha (partition elimination), reduciendo dramáticamente el I/O en tablas históricas grandes.
Capítulo 8: Ejercicios Prácticos Resueltos
Los siguientes ejercicios están basados en un modelo de datos de ventas. Se asume la existencia de las tablas: ventas, clientes, productos, regiones.
Ejercicio 1 – Top 3 Productos por Región (Ventanas)
Objetivo: Obtener los 3 productos con mayor volumen de ventas por cada región, mostrando también el porcentaje que representan del total regional.
Solución con RANK y porcentaje regional
WITH ventas_regionales AS (
SELECT
r.nombre_region,
p.nombre_producto,
SUM(v.monto) AS total_venta,
COUNT(*) AS cant_transacciones
FROM ventas v
JOIN clientes c ON v.cliente_id = c.cliente_id
JOIN regiones r ON c.region_id = r.region_id
JOIN productos p ON v.producto_id = p.producto_id
WHERE v.fecha BETWEEN DATE '2025-01-01' AND DATE '2025-12-31'
GROUP BY 1, 2
),
ranking AS (
SELECT
nombre_region,
nombre_producto,
total_venta,
cant_transacciones,
RANK() OVER (PARTITION BY nombre_region ORDER BY total_venta DESC) AS rank_prod,
SUM(total_venta) OVER (PARTITION BY nombre_region) AS total_region,
CAST(total_venta * 100.0 / SUM(total_venta) OVER (PARTITION BY nombre_region)
AS DECIMAL(5,2)) AS pct_region
FROM ventas_regionales
)
SELECT * FROM ranking
WHERE rank_prod <= 3
ORDER BY nombre_region, rank_prod;
Ejercicio 2 – Detección de Gaps en Series Temporales
Objetivo: Identificar días sin ventas (gaps) en una tabla de ventas diarias, útil para auditorías y controles de calidad de datos.
Solución con LAG y generación de calendario
WITH serie_ventas AS (
SELECT
fecha,
SUM(monto) AS total_dia,
LAG(fecha, 1) OVER (ORDER BY fecha) AS fecha_anterior
FROM ventas
WHERE fecha BETWEEN DATE '2025-01-01' AND DATE '2025-12-31'
GROUP BY fecha
)
SELECT
fecha_anterior + 1 AS gap_inicio,
fecha - 1 AS gap_fin,
(fecha - fecha_anterior - 1) AS dias_sin_ventas
FROM serie_ventas
WHERE (fecha - fecha_anterior) > 1 -- Hay un gap
ORDER BY gap_inicio;
Ejercicio 3 – Cohorte de Clientes por Mes de Primera Compra
Objetivo: Analizar la retención de clientes agrupándolos por el mes de su primera compra (cohorte) y medir cuántos siguen comprando en meses posteriores.
Análisis de cohortes con FIRST_VALUE y GROUP BY
WITH primera_compra AS (
SELECT
cliente_id,
CAST(FIRST_VALUE(fecha) OVER (
PARTITION BY cliente_id
ORDER BY fecha
ROWS UNBOUNDED PRECEDING
) AS CHAR(7)) AS cohorte_mes -- 'YYYY-MM'
FROM ventas
),
actividad AS (
SELECT
v.cliente_id,
CAST(v.fecha AS CHAR(7)) AS mes_actividad,
pc.cohorte_mes
FROM ventas v
JOIN primera_compra pc ON v.cliente_id = pc.cliente_id
GROUP BY 1, 2, 3
)
SELECT
cohorte_mes,
mes_actividad,
COUNT(DISTINCT cliente_id) AS clientes_activos,
-- Diferencia en meses desde el inicio de la cohorte
(CAST(mes_actividad AS DATE FORMAT 'YYYY-MM') -
CAST(cohorte_mes AS DATE FORMAT 'YYYY-MM')) MONTH AS mes_numero
FROM actividad
GROUP BY 1, 2
ORDER BY 1, 4;
Ejercicio 4 – Cálculo de Percentiles con QUANTILE
QUANTILE – función exclusiva de Teradata
-- QUANTILE divide el resultado en N grupos de igual tamaño
-- Similar a NTILE pero con semántica estadística más precisa
SELECT
cliente_id,
total_compras,
-- Deciles (0 al 9)
QUANTILE(10, total_compras OVER ()) AS decil,
-- Percentil 25, 50, 75 (cuartiles)
QUANTILE(100, total_compras OVER ()) AS percentil
FROM (
SELECT cliente_id, SUM(monto) AS total_compras
FROM ventas
GROUP BY cliente_id
) sub
ORDER BY decil DESC, total_compras DESC;
Capítulo 9: Vistas, Macros y Procedimientos Almacenados
9.1 Vistas (VIEWS)
CREATE VIEW – abstracción de consultas complejas
-- Vista simple de clientes activos
REPLACE VIEW vw_clientes_activos AS
SELECT
c.cliente_id,
c.nombre,
c.email,
MAX(v.fecha) AS ultima_compra,
SUM(v.monto) AS total_historico
FROM clientes c
JOIN ventas v ON c.cliente_id = v.cliente_id
WHERE c.activo = 1
GROUP BY 1,2,3;
-- Uso de la vista
SELECT * FROM vw_clientes_activos
WHERE ultima_compra >= DATE - 90;
9.2 Macros
Las macros en Teradata son como procedimientos parametrizados pero más simples. Se compilan una vez y se ejecutan con EXEC. Son ideales para consultas recurrentes con parámetros.
CREATE MACRO con parámetros
REPLACE MACRO mac_ventas_cliente (
p_cliente_id INTEGER,
p_fecha_desde DATE,
p_fecha_hasta DATE
) AS (
SELECT
fecha,
producto_id,
monto
FROM ventas
WHERE cliente_id = :p_cliente_id
AND fecha BETWEEN :p_fecha_desde AND :p_fecha_hasta
ORDER BY fecha;
);
-- Ejecución
EXEC mac_ventas_cliente (500, DATE '2025-01-01', DATE '2025-06-30');
9.3 Procedimientos Almacenados (Stored Procedures)
CREATE PROCEDURE con lógica condicional y cursores
REPLACE PROCEDURE sp_actualizar_segmento (
IN p_fecha_corte DATE,
OUT p_total_actualizados INTEGER
)
BEGIN
DECLARE v_cliente_id INTEGER;
DECLARE v_total DECIMAL(15,2);
DECLARE SQLSTATE VARCHAR(6) DEFAULT '00000';
-- Cursor para iterar clientes
DECLARE cur_clientes CURSOR FOR
SELECT cliente_id, SUM(monto) AS total_anio
FROM ventas
WHERE fecha BETWEEN p_fecha_corte - 365 AND p_fecha_corte
GROUP BY cliente_id;
SET p_total_actualizados = 0;
OPEN cur_clientes;
FETCH cur_clientes INTO v_cliente_id, v_total;
WHILE SQLSTATE = '00000' DO
UPDATE clientes SET
segmento = CASE
WHEN v_total >= 100000 THEN 'PLATINUM'
WHEN v_total >= 50000 THEN 'GOLD'
WHEN v_total >= 10000 THEN 'SILVER'
ELSE 'STANDARD'
END
WHERE cliente_id = v_cliente_id;
SET p_total_actualizados = p_total_actualizados + 1;
FETCH cur_clientes INTO v_cliente_id, v_total;
END WHILE;
CLOSE cur_clientes;
END;
Capítulo 10: Referencia Rápida y Diferencias con Otros Motores
10.1 Diferencias Clave: Teradata vs SQL Estándar / Oracle / SQL Server
| Concepto | Teradata | Oracle / SQL Server |
|---|---|---|
| Fecha actual | DATE | SYSDATE / GETDATE() |
| Auto-incremento | No nativo – usar IDENTITY o secuencias | SEQUENCE / IDENTITY |
| Top N filas | SAMPLE n o TOP n (BTEQ) | ROWNUM / TOP n / FETCH FIRST |
| Concatenar strings | || o CONCAT() | + (SQL Server) / || (Oracle) |
| Manejo de nulos en aritmética | NULL propaga / NULLIFZERO() | NVL / ISNULL |
| Tabla dual | No existe – SELECT 1 directamente | DUAL (Oracle) |
| UPSERT | MERGE INTO … WHEN MATCHED | MERGE INTO (similar) |
| Tabla temporal de sesión | CREATE VOLATILE TABLE | CREATE #temp (SQL Server) |
| Índice de acceso rápido | Índice Primario (PI) distribuido | Clustered Index / IOT |
10.2 Comandos de Administración Frecuentes
Comandos de utilidad en el día a día
-- Ver estructura de una tabla
SHOW TABLE nombre_tabla;
HELP TABLE nombre_tabla;
HELP COLUMN nombre_tabla.*;
-- Ver espacio utilizado
SELECT * FROM DBC.TableSizeV WHERE DatabaseName = 'mi_base';
-- Ver sesiones activas
SELECT * FROM DBC.SessionInfoV ORDER BY SpoolUsage DESC;
-- Ver locks activos
SELECT * FROM DBC.LockLogV;
-- Ver objetos de una base de datos
SELECT TableName, TableKind FROM DBC.TablesV
WHERE DatabaseName = 'mi_base' ORDER BY TableKind, TableName;
-- Ver dependencias de una vista
SELECT * FROM DBC.ObjectUsageV WHERE ObjectDatabaseName = 'mi_base';
10.3 Tablas Volátiles y Tablas Derivadas (CTEs)
Tabla Volátil vs CTE vs Tabla Derivada
-- 1. TABLA VOLÁTIL: persiste durante la sesión, con estadísticas
CREATE VOLATILE TABLE tmp_resumen AS (
SELECT cliente_id, SUM(monto) AS total
FROM ventas WHERE fecha >= DATE - 90
GROUP BY cliente_id
) WITH DATA PRIMARY INDEX (cliente_id) ON COMMIT PRESERVE ROWS;
COLLECT STATISTICS ON tmp_resumen COLUMN (cliente_id);
-- 2. CTE (WITH): más legible, no materializada físicamente
WITH resumen AS (
SELECT cliente_id, SUM(monto) AS total
FROM ventas WHERE fecha >= DATE - 90
GROUP BY cliente_id
)
SELECT c.nombre, r.total FROM resumen r JOIN clientes c ON r.cliente_id = c.cliente_id;
-- 3. TABLA DERIVADA: subconsulta en el FROM
SELECT c.nombre, r.total
FROM clientes c
JOIN (SELECT cliente_id, SUM(monto) AS total FROM ventas GROUP BY 1) r
ON c.cliente_id = r.cliente_id;
⚠ Performance: Las tablas volátiles son ideales cuando la subconsulta se reutiliza múltiples veces o cuando el optimizador no la resuelve bien como CTE. Siempre recolectar estadísticas sobre las columnas de JOIN de las tablas volátiles.
Capítulo 11: FastLoad y MultiLoad – Carga Masiva de Datos
Teradata ofrece utilidades de carga masiva diseñadas para superar las limitaciones del SQL convencional cuando se trabaja con millones de registros. FastLoad y MultiLoad son las herramientas fundamentales del ecosistema ETL de Teradata.
11.1 Comparativa: FastLoad vs MultiLoad vs BTEQ
| Característica | FastLoad | MultiLoad | BTEQ (INSERT) |
|---|---|---|---|
| Velocidad de carga | Máxima (todos los AMPs) | Muy alta | Lenta (secuencial) |
| Tabla destino | Debe estar vacía | Con o sin datos | Con o sin datos |
| Operaciones soportadas | Solo INSERT | INSERT/UPDATE/DELETE/UPSERT | Todas las DML |
| Acceso durante carga | Tabla NO disponible | Tabla NO disponible | Tabla disponible |
| Tabla de errores automática | Sí (2 tablas) | Sí (2 tablas) | No |
| Rollback automático | Sí (con restart) | Sí (con checkpoint) | No por defecto |
| Volumen ideal | > 100.000 filas | > 50.000 filas mixtas | < 10.000 filas |
| Paralelismo | Total: todos los AMPs | Total: todos los AMPs | Secuencial |
| Usa Spool | No | Work tables internas | Sí |
FastLoad exige tabla VACÍA y sin índices secundarios activos durante la carga. MultiLoad tolera datos existentes pero no permite acceso concurrente durante el proceso.
11.2 FastLoad – Carga Inicial Masiva
FastLoad opera en dos fases: Acquisition Phase (distribuye datos a los AMPs) y Application Phase (escribe en los discos). Puede cargar millones de filas en minutos sobre una tabla vacía.
Paso 1 – Preparación del entorno
Crear tablas de destino y limpiar errores previos
-- Tabla destino (debe estar vacía)
CREATE MULTISET TABLE ecomm.pedidos_stg,
NO FALLBACK, NO BEFORE JOURNAL, NO AFTER JOURNAL
(
pedido_id INTEGER NOT NULL,
cliente_id INTEGER NOT NULL,
producto_id INTEGER NOT NULL,
fecha_pedido DATE FORMAT 'YYYY-MM-DD',
cantidad SMALLINT,
precio_unitario DECIMAL(10,2),
monto_total DECIMAL(12,2),
estado VARCHAR(20),
canal VARCHAR(30),
region VARCHAR(50)
)
PRIMARY INDEX (pedido_id);
-- Limpiar tablas de error previas si existieran
DROP TABLE ecomm.pedidos_stg_ET; -- Error Table 1: errores de conversión
DROP TABLE ecomm.pedidos_stg_UV; -- Error Table 2: violaciones de unicidad
Paso 2 – Script FastLoad completo
fastload_pedidos.fl – Script de carga 1.000.000 filas
/*================================================
FastLoad: Carga inicial de PEDIDOS
Archivo: pedidos_1M.csv (1.000.000 filas)
Destino: ecomm.pedidos_stg
================================================*/
SESSIONS 8; /* Un thread por AMP/CPU aproximadamente */
ERRLIMIT 100; /* Abortar si supera 100 errores */
LOGON tdserver/usuario,contraseña;
BEGIN LOADING ecomm.pedidos_stg
ERRORFILES ecomm.pedidos_stg_ET, ecomm.pedidos_stg_UV
CHECKPOINT 50000; /* Restart cada 50.000 filas */
DEFINE
pedido_id (CHAR(10)),
cliente_id (CHAR(10)),
producto_id (CHAR(10)),
fecha_pedido (CHAR(10)),
cantidad (CHAR(5)),
precio_unitario (CHAR(12)),
monto_total (CHAR(14)),
estado (CHAR(20)),
canal (CHAR(30)),
region (CHAR(50))
FILE = '/data/pedidos_1M.csv'
SKIP 1; /* Saltar línea de encabezado */
INSERT INTO ecomm.pedidos_stg (
pedido_id, cliente_id, producto_id,
fecha_pedido, cantidad, precio_unitario,
monto_total, estado, canal, region
)
VALUES (
:pedido_id (INTEGER),
:cliente_id (INTEGER),
:producto_id (INTEGER),
:fecha_pedido (DATE, FORMAT 'YYYY-MM-DD'),
:cantidad (SMALLINT),
:precio_unitario (DECIMAL(10,2)),
:monto_total (DECIMAL(12,2)),
:estado,
:canal,
:region
);
END LOADING;
SELECT COUNT(*) FROM ecomm.pedidos_stg;
SELECT COUNT(*) FROM ecomm.pedidos_stg_ET;
SELECT COUNT(*) FROM ecomm.pedidos_stg_UV;
LOGOFF;
Paso 3 – Monitoreo y troubleshooting
Diagnóstico de una carga FastLoad activa o fallida
-- Ver sesiones activas de utilidades de carga
SELECT UserName, ClientAddr, Workload
FROM DBC.SessionInfoV
WHERE Workload = 'FASTLOAD';
-- Errores más frecuentes
SELECT ErrorCode, TRIM(ErrorMsg) AS descripcion, COUNT(*) AS cant
FROM ecomm.pedidos_stg_ET
GROUP BY 1, 2
ORDER BY 3 DESC;
-- Ver una fila con error para diagnóstico
SELECT * FROM ecomm.pedidos_stg_ET
WHERE ErrorCode = 2679 /* 2679 = error de conversión de fecha */
SAMPLE 5;
11.3 MultiLoad – Carga Incremental con Upsert y Delete
MultiLoad permite mezclar INSERT, UPDATE, DELETE y UPSERT en una misma ejecución sobre tablas que ya contienen datos. Es ideal para cargas incrementales diarias y sincronización de datos.
multiload_pedidos_delta.ml – Delta diario con UPSERT y DELETE
/*================================================
MultiLoad: Delta diario de PEDIDOS
Lógica: UPSERT nuevos/modificados + DELETE cancelados
Archivo: pedidos_delta_YYYYMMDD.csv
Campo tipo_operacion: I=Insert, U=Update, D=Delete
================================================*/
SESSIONS 4;
ERRLIMIT 50;
LOGON tdserver/usuario,contraseña;
.BEGIN MLOAD TABLES ecomm.pedidos
WORKTABLES ecomm.pedidos_wt1, ecomm.pedidos_wt2
ERRORTABLES ecomm.pedidos_ET1, ecomm.pedidos_ET2
CHECKPOINT 25000;
.LAYOUT delta_pedidos;
.FIELD pedido_id * CHAR(10);
.FIELD cliente_id * CHAR(10);
.FIELD producto_id * CHAR(10);
.FIELD fecha_pedido * CHAR(10);
.FIELD cantidad * CHAR(5);
.FIELD precio_unitario * CHAR(12);
.FIELD monto_total * CHAR(14);
.FIELD estado * CHAR(20);
.FIELD canal * CHAR(30);
.FIELD region * CHAR(50);
.FIELD tipo_op * CHAR(1);
.DML LABEL upsert_pedidos IGNORE DUPLICATE ROWS;
UPDATE ecomm.pedidos SET
cantidad = :cantidad (SMALLINT),
precio_unitario = :precio_unitario (DECIMAL(10,2)),
monto_total = :monto_total (DECIMAL(12,2)),
estado = :estado
WHERE pedido_id = :pedido_id (INTEGER)
AND :tipo_op = 'U';
INSERT INTO ecomm.pedidos
SELECT :pedido_id (INTEGER), :cliente_id (INTEGER),
:producto_id (INTEGER),
:fecha_pedido (DATE, FORMAT 'YYYY-MM-DD'),
:cantidad (SMALLINT), :precio_unitario (DECIMAL(10,2)),
:monto_total (DECIMAL(12,2)), :estado, :canal, :region
WHERE :tipo_op = 'I';
.DML LABEL delete_pedidos;
DELETE FROM ecomm.pedidos
WHERE pedido_id = :pedido_id (INTEGER)
AND :tipo_op = 'D';
.IMPORT INFILE /data/pedidos_delta.csv
LAYOUT delta_pedidos
APPLY upsert_pedidos WHERE tipo_op IN ('I','U')
APPLY delete_pedidos WHERE tipo_op = 'D'
SKIP 1;
.END MLOAD;
SELECT COUNT(*) AS total_final FROM ecomm.pedidos;
SELECT COUNT(*) AS errores_ET1 FROM ecomm.pedidos_ET1;
SELECT COUNT(*) AS errores_ET2 FROM ecomm.pedidos_ET2;
LOGOFF;
11.4 TPT – Teradata Parallel Transporter (Estándar Moderno)
TPT unifica FastLoad, MultiLoad y otros operadores en un framework único. Es el estándar recomendado para nuevas implementaciones desde Teradata 13.x en adelante.
Script TPT equivalente a FastLoad
DEFINE JOB carga_pedidos_tpt
DESCRIPTION 'Carga masiva de pedidos via TPT'
(
DEFINE SCHEMA pedidos_schema
(
pedido_id VARCHAR(10),
cliente_id VARCHAR(10),
fecha_pedido VARCHAR(10),
monto_total VARCHAR(14),
estado VARCHAR(20)
);
DEFINE OPERATOR file_reader TYPE DATACONNECTOR PRODUCER
SCHEMA pedidos_schema
ATTRIBUTES
(
VARCHAR FileName = '/data/pedidos_1M.csv',
VARCHAR Format = 'Delimited',
VARCHAR TextDelimiter = ',',
VARCHAR SkipRows = '1'
);
DEFINE OPERATOR td_loader TYPE LOAD
SCHEMA pedidos_schema
ATTRIBUTES
(
VARCHAR TdpId = 'tdserver',
VARCHAR UserName = 'usuario',
VARCHAR UserPassword = 'contraseña',
VARCHAR TargetTable = 'ecomm.pedidos_stg',
VARCHAR ErrorTable1 = 'ecomm.pedidos_stg_ET',
VARCHAR ErrorTable2 = 'ecomm.pedidos_stg_UV',
INTEGER Sessions = 8
);
STEP cargar_datos
(
APPLY
('INSERT INTO ecomm.pedidos_stg VALUES (
:pedido_id (INTEGER), :cliente_id (INTEGER),
:fecha_pedido (DATE, FORMAT ''YYYY-MM-DD''),
:monto_total (DECIMAL(12,2)), :estado);'
)
TO OPERATOR (td_loader)
SELECT * FROM OPERATOR (file_reader);
);
);
Capítulo 12: Dataset E-Commerce – 1.000.000 Filas para Práctica
Este capítulo presenta el dataset completo de e-commerce para practicar todos los conceptos del manual. Incluye modelo de datos, scripts de generación sintética en Python, procedimiento de carga y 14 consultas analíticas de complejidad progresiva.
12.1 Modelo de Datos – Esquema Estrella
| Tabla | Tipo | Filas aprox. | Descripción |
|---|---|---|---|
| ecomm.clientes | Dimensión | 50.000 | Datos maestros de clientes con segmento y región |
| ecomm.productos | Dimensión | 5.000 | Catálogo con categoría, marca y precio base |
| ecomm.pedidos | Hecho | 1.000.000 | Tabla central: una fila por línea de pedido |
| ecomm.pagos | Hecho | 950.000 | Pagos de pedidos (excluye cancelados) |
| ecomm.devoluciones | Hecho | 80.000 | Devoluciones (~8% de los pedidos entregados) |
12.2 DDL Completo – Creación de Tablas
ecomm.clientes – Dimensión principal
CREATE SET TABLE ecomm.clientes,
NO FALLBACK, NO BEFORE JOURNAL, NO AFTER JOURNAL
(
cliente_id INTEGER NOT NULL,
nombre VARCHAR(100) NOT NULL,
email VARCHAR(150),
pais VARCHAR(50) DEFAULT 'Argentina',
region VARCHAR(50),
ciudad VARCHAR(80),
segmento VARCHAR(20), /* STANDARD|SILVER|GOLD|PLATINUM */
fecha_alta DATE FORMAT 'YYYY-MM-DD',
fecha_nacimiento DATE FORMAT 'YYYY-MM-DD',
genero CHAR(1), /* M|F|X */
activo BYTEINT DEFAULT 1
)
PRIMARY INDEX (cliente_id);
CREATE INDEX (region) ON ecomm.clientes;
ecomm.productos – Dimensión catálogo
CREATE SET TABLE ecomm.productos,
NO FALLBACK, NO BEFORE JOURNAL, NO AFTER JOURNAL
(
producto_id INTEGER NOT NULL,
nombre VARCHAR(200) NOT NULL,
categoria VARCHAR(60),
subcategoria VARCHAR(60),
marca VARCHAR(60),
precio_base DECIMAL(10,2),
costo DECIMAL(10,2),
activo BYTEINT DEFAULT 1
)
PRIMARY INDEX (producto_id);
CREATE INDEX (categoria) ON ecomm.productos;
ecomm.pedidos – Tabla de Hechos con PPI mensual
CREATE MULTISET TABLE ecomm.pedidos,
NO FALLBACK, NO BEFORE JOURNAL, NO AFTER JOURNAL
(
pedido_id INTEGER NOT NULL,
cliente_id INTEGER NOT NULL,
producto_id INTEGER NOT NULL,
fecha_pedido DATE NOT NULL FORMAT 'YYYY-MM-DD',
cantidad SMALLINT DEFAULT 1,
precio_unitario DECIMAL(10,2),
descuento_pct DECIMAL(5,2) DEFAULT 0,
monto_bruto DECIMAL(12,2),
monto_descuento DECIMAL(12,2) DEFAULT 0,
monto_total DECIMAL(12,2),
estado VARCHAR(20), /* PENDIENTE|ENVIADO|ENTREGADO|CANCELADO */
canal VARCHAR(30), /* WEB|APP|MKTPLACE|TELEVENTAS */
region VARCHAR(50),
tiempo_entrega SMALLINT /* días hasta entrega, 0 si no entregado */
)
PRIMARY INDEX (pedido_id)
PARTITION BY RANGE_N(
fecha_pedido BETWEEN DATE '2022-01-01' AND DATE '2025-12-31'
EACH INTERVAL '1' MONTH
);
CREATE INDEX (cliente_id) ON ecomm.pedidos;
ecomm.pagos y ecomm.devoluciones
CREATE MULTISET TABLE ecomm.pagos,
NO FALLBACK, NO BEFORE JOURNAL, NO AFTER JOURNAL
(
pago_id INTEGER NOT NULL,
pedido_id INTEGER NOT NULL,
cliente_id INTEGER NOT NULL,
fecha_pago DATE FORMAT 'YYYY-MM-DD',
monto_pagado DECIMAL(12,2),
metodo_pago VARCHAR(30), /* TARJETA|TRANSFERENCIA|MP|CRYPTO */
cuotas BYTEINT DEFAULT 1,
estado_pago VARCHAR(20) /* APROBADO|RECHAZADO|PENDIENTE */
)
PRIMARY INDEX (pago_id);
CREATE INDEX (pedido_id) ON ecomm.pagos;
CREATE MULTISET TABLE ecomm.devoluciones,
NO FALLBACK, NO BEFORE JOURNAL, NO AFTER JOURNAL
(
devolucion_id INTEGER NOT NULL,
pedido_id INTEGER NOT NULL,
cliente_id INTEGER NOT NULL,
fecha_devol DATE FORMAT 'YYYY-MM-DD',
motivo VARCHAR(100),
monto_devuelto DECIMAL(12,2),
estado_devol VARCHAR(20) /* APROBADA|RECHAZADA|EN_PROCESO */
)
PRIMARY INDEX (devolucion_id);
12.3 Script Python – Generación de 1.000.000 Filas Sintéticas
generar_dataset_ecomm.py - Se descaga el script y dataset, adjunto con los Ejercicios
# generar_dataset_ecomm.py
# Manual Teradata -- Capitulo 12
# Descripcion: Genera los archivos CSV del dataset de e-commerce.
#
# PRODUCTOS: usa el catalogo real de gondola argentina
# con precios, marcas y descripciones reales.
# Si no encuentra el archivo, usa datos sinteticos de respaldo.
#
# Archivos generados:
# clientes.csv --> 50.000 filas
# productos.csv --> 5.000 filas (sample del catalogo real)
# pedidos_1M.csv --> 1.000.000 filas
# pagos.csv --> ~950.000 filas (excluye cancelados)
#
# Uso:
# python generar_dataset_ecomm.py
#
# Requisitos: Python 3.8+, solo stdlib
import csv
import random
import datetime
import sys
import os
from pathlib import Path
random.seed(42)
.................
......................
..........................
12.4 Secuencia de Carga y Post-Carga
Orden recomendado: dimensiones → hechos → estadísticas
-- PASO 1: Dimensiones (primero siempre)
-- fastload < fastload_clientes.fl
-- fastload < fastload_productos.fl
-- PASO 2: Tabla de hechos principal
-- fastload < fastload_pedidos.fl
-- PASO 3: Hechos secundarios
-- fastload < fastload_pagos.fl
-- PASO 4: Recolectar estadísticas (OBLIGATORIO post-carga)
COLLECT STATISTICS ON ecomm.pedidos COLUMN (pedido_id);
COLLECT STATISTICS ON ecomm.pedidos COLUMN (cliente_id);
COLLECT STATISTICS ON ecomm.pedidos COLUMN (fecha_pedido);
COLLECT STATISTICS ON ecomm.pedidos COLUMN (estado);
COLLECT STATISTICS ON ecomm.pedidos COLUMN (canal);
COLLECT STATISTICS ON ecomm.clientes COLUMN (cliente_id);
COLLECT STATISTICS ON ecomm.clientes COLUMN (region);
COLLECT STATISTICS ON ecomm.clientes COLUMN (segmento);
COLLECT STATISTICS ON ecomm.productos COLUMN (producto_id);
COLLECT STATISTICS ON ecomm.productos COLUMN (categoria);
-- PASO 5: Verificación final
SELECT 'clientes' AS tabla, COUNT(*) AS filas FROM ecomm.clientes
UNION ALL SELECT 'productos', COUNT(*) FROM ecomm.productos
UNION ALL SELECT 'pedidos', COUNT(*) FROM ecomm.pedidos
UNION ALL SELECT 'pagos', COUNT(*) FROM ecomm.pagos;
12.5 Consultas Analíticas sobre el Dataset
Las siguientes consultas están diseñadas para practicar todos los conceptos del manual sobre el dataset real, de menor a mayor complejidad.
NIVEL 1 – Consultas Básicas de Exploración
Q01 – Resumen general del dataset
SELECT
COUNT(DISTINCT cliente_id) AS clientes_activos,
COUNT(*) AS total_pedidos,
SUM(monto_total) AS facturacion_total,
AVG(monto_total) AS ticket_promedio,
MIN(fecha_pedido) AS primer_pedido,
MAX(fecha_pedido) AS ultimo_pedido
FROM ecomm.pedidos
WHERE estado <> 'CANCELADO';
Q02 – Ventas por canal y estado con tiempo de entrega
SELECT
canal,
estado,
COUNT(*) AS cantidad,
SUM(monto_total) AS monto_total,
AVG(monto_total) AS ticket_prom,
AVG(tiempo_entrega) AS dias_entrega_prom
FROM ecomm.pedidos
GROUP BY canal, estado
ORDER BY canal, 4 DESC;
Q03 – Top categorías: facturación, descuentos y margen estimado
SELECT
pr.categoria,
COUNT(*) AS cant_pedidos,
SUM(p.monto_total) AS facturacion,
SUM(p.monto_descuento) AS total_descuentos,
CAST(SUM(p.monto_descuento)*100.0
/ NULLIFZERO(SUM(p.monto_bruto)) AS DECIMAL(5,2)) AS pct_desc,
SUM((p.precio_unitario - pr.costo)
* p.cantidad) AS margen_estimado
FROM ecomm.pedidos p
JOIN ecomm.productos pr ON p.producto_id = pr.producto_id
WHERE p.estado <> 'CANCELADO'
GROUP BY pr.categoria
ORDER BY facturacion DESC;
NIVEL 2 – Funciones Analíticas y Temporales
Q04 – Evolución mensual con variación % y acumulado anual
WITH mensual AS (
SELECT
EXTRACT(YEAR FROM fecha_pedido) AS anio,
EXTRACT(MONTH FROM fecha_pedido) AS mes,
SUM(monto_total) AS facturacion,
COUNT(*) AS pedidos,
COUNT(DISTINCT cliente_id) AS clientes_unicos
FROM ecomm.pedidos
WHERE estado <> 'CANCELADO'
GROUP BY 1, 2
)
SELECT
anio, mes, facturacion, pedidos, clientes_unicos,
LAG(facturacion,1) OVER (ORDER BY anio, mes) AS facturacion_mes_ant,
CAST(
(facturacion - LAG(facturacion,1) OVER (ORDER BY anio, mes))*100.0
/ NULLIFZERO(LAG(facturacion,1) OVER (ORDER BY anio, mes))
AS DECIMAL(6,2)) AS var_pct_mom,
SUM(facturacion) OVER (PARTITION BY anio
ORDER BY mes ROWS UNBOUNDED PRECEDING) AS acum_anual
FROM mensual
ORDER BY anio, mes;
Q05 – Top 3 productos por categoría con porcentaje de participación
WITH ventas_prod AS (
SELECT
pr.categoria,
pr.nombre AS producto,
SUM(p.monto_total) AS facturacion,
COUNT(*) AS cant_pedidos
FROM ecomm.pedidos p
JOIN ecomm.productos pr ON p.producto_id = pr.producto_id
WHERE p.estado <> 'CANCELADO'
GROUP BY 1, 2
)
SELECT * FROM (
SELECT
categoria, producto, facturacion, cant_pedidos,
RANK() OVER (PARTITION BY categoria
ORDER BY facturacion DESC) AS rank_cat,
CAST(facturacion*100.0
/ SUM(facturacion) OVER (PARTITION BY categoria)
AS DECIMAL(5,2)) AS pct_categoria
FROM ventas_prod
) r
WHERE rank_cat <= 3
ORDER BY categoria, rank_cat;
Q06 – Media móvil 7 días y detección de días atípicos
WITH diario AS (
SELECT fecha_pedido,
COUNT(*) AS pedidos, SUM(monto_total) AS monto
FROM ecomm.pedidos
WHERE estado <> 'CANCELADO'
AND fecha_pedido BETWEEN DATE '2024-01-01' AND DATE '2024-12-31'
GROUP BY fecha_pedido
),
con_media AS (
SELECT
fecha_pedido, pedidos, monto,
AVG(monto) OVER (ORDER BY fecha_pedido
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS mm7d,
STDDEV_POP(monto) OVER (ORDER BY fecha_pedido
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS std7d
FROM diario
)
SELECT
fecha_pedido, pedidos, monto, mm7d,
CASE WHEN ABS(monto - mm7d) > 2 * NULLIFZERO(std7d)
THEN 'ANOMALIA' ELSE 'NORMAL' END AS clasificacion
FROM con_media
ORDER BY fecha_pedido;
NIVEL 3 – Análisis de Clientes y Segmentación
Q07 – Segmentación RFM completa
WITH rfm_base AS (
SELECT
cliente_id,
ZEROIFNULL((MAX(fecha_pedido) - DATE) * -1) AS recencia,
COUNT(*) AS frecuencia,
SUM(monto_total) AS monetario
FROM ecomm.pedidos
WHERE estado <> 'CANCELADO'
GROUP BY cliente_id
),
scores AS (
SELECT
cliente_id,
recencia,
frecuencia,
monetario,
RANK() OVER (ORDER BY recencia ASC) AS r_score,
RANK() OVER (ORDER BY frecuencia DESC) AS f_score,
RANK() OVER (ORDER BY monetario DESC) AS m_score
FROM rfm_base
)
SELECT
cliente_id, recencia, frecuencia, monetario,
r_score, f_score, m_score,
r_score + f_score + m_score AS rfm_total,
CASE
WHEN r_score >= 4 AND f_score >= 4 THEN 'CHAMPION'
WHEN r_score >= 3 AND f_score >= 3 THEN 'LOYAL'
WHEN r_score >= 4 AND f_score <= 2 THEN 'NEW_CUSTOMER'
WHEN r_score <= 2 AND f_score >= 3 THEN 'AT_RISK'
WHEN r_score = 1 AND f_score = 1 THEN 'LOST'
ELSE 'NEEDS_ATTENTION'
END AS segmento_rfm
FROM scores
ORDER BY 8 DESC;
Q08 – Análisis de cohortes: retención mensual
WITH primera AS (
SELECT cliente_id,
EXTRACT(YEAR FROM MIN(fecha_pedido))*100
+ EXTRACT(MONTH FROM MIN(fecha_pedido)) AS cohorte
FROM ecomm.pedidos WHERE estado <> 'CANCELADO'
GROUP BY 1
),
act AS (
SELECT DISTINCT p.cliente_id, pr.cohorte,
EXTRACT(YEAR FROM p.fecha_pedido)*100
+ EXTRACT(MONTH FROM p.fecha_pedido) AS mes_act
FROM ecomm.pedidos p
JOIN primera pr ON p.cliente_id = pr.cliente_id
WHERE p.estado <> 'CANCELADO'
)
SELECT
cohorte,
mes_act,
COUNT(DISTINCT cliente_id) AS clientes,
(mes_act/100*12 + MOD(mes_act,100))
- (cohorte/100*12 + MOD(cohorte,100)) AS mes_numero
FROM act
GROUP BY 1, 2
ORDER BY 1, 4;
Q09 – Detección de clientes en riesgo de churn (GOLD y PLATINUM)
WITH act AS (
SELECT cliente_id,
DATE - MAX(fecha_pedido) AS dias_inactivo,
COUNT(*) AS total_pedidos,
SUM(CASE WHEN fecha_pedido >= DATE-180 THEN monto_total ELSE 0 END) AS m_ult6m,
SUM(CASE WHEN fecha_pedido BETWEEN DATE-360 AND DATE-181
THEN monto_total ELSE 0 END) AS m_ant6m
FROM ecomm.pedidos WHERE estado <> 'CANCELADO'
GROUP BY 1
)
SELECT
a.cliente_id, c.nombre, c.segmento, c.region,
dias_inactivo, total_pedidos, m_ult6m, m_ant6m,
CAST((m_ult6m-m_ant6m)*100.0/NULLIFZERO(m_ant6m) AS DECIMAL(6,2)) AS var_gasto_pct,
CASE
WHEN dias_inactivo > 180 THEN 'CHURN_ALTO'
WHEN dias_inactivo > 90 AND m_ult6m < m_ant6m*0.5 THEN 'CHURN_MEDIO'
ELSE 'CHURN_BAJO'
END AS riesgo_churn
FROM act a
JOIN ecomm.clientes c ON a.cliente_id = c.cliente_id
WHERE dias_inactivo > 60
AND c.segmento IN ('GOLD','PLATINUM')
ORDER BY dias_inactivo DESC;
NIVEL 4 – Análisis Avanzado Multi-tabla
Q10 – Tasa de devolución por categoría y región
SELECT
pr.categoria,
p.region,
COUNT(DISTINCT p.pedido_id) AS total_entregados,
COUNT(DISTINCT d.pedido_id) AS devueltos,
CAST(COUNT(DISTINCT d.pedido_id)*100.0
/ NULLIFZERO(COUNT(DISTINCT p.pedido_id)) AS DECIMAL(5,2)) AS tasa_devol_pct,
SUM(d.monto_devuelto) AS monto_devuelto
FROM ecomm.pedidos p
JOIN ecomm.productos pr ON p.producto_id = pr.producto_id
LEFT JOIN ecomm.devoluciones d ON p.pedido_id = d.pedido_id
WHERE p.estado = 'ENTREGADO'
GROUP BY 1, 2
ORDER BY tasa_devol_pct DESC;
Q11 – LTV proyectado a 12 meses por segmento y región
WITH metricas AS (
SELECT p.cliente_id, c.segmento, c.region,
COUNT(*) AS freq,
SUM(p.monto_total) AS ltv_hist,
AVG(p.monto_total) AS aov,
COUNT(*)*1.0/NULLIFZERO(
MONTHS_BETWEEN(DATE,MIN(p.fecha_pedido))) AS freq_mensual
FROM ecomm.pedidos p
JOIN ecomm.clientes c ON p.cliente_id=c.cliente_id
WHERE p.estado <> 'CANCELADO'
GROUP BY 1,2,3
)
SELECT segmento, region,
COUNT(*) AS clientes,
AVG(ltv_hist) AS ltv_prom,
AVG(aov) AS aov_prom,
CAST(AVG(aov)*AVG(freq_mensual)*12 AS DECIMAL(15,2)) AS ltv_12m,
CAST(AVG(aov)*AVG(freq_mensual)*12*0.55 AS DECIMAL(15,2)) AS margen_12m
FROM metricas
GROUP BY 1,2
ORDER BY ltv_12m DESC;
Q12 – Análisis de métodos de pago y tasa de aprobación
SELECT
metodo_pago, cuotas,
COUNT(*) AS total,
SUM(CASE WHEN estado_pago='APROBADO' THEN 1 ELSE 0 END) AS aprobados,
SUM(CASE WHEN estado_pago='RECHAZADO' THEN 1 ELSE 0 END) AS rechazados,
CAST(SUM(CASE WHEN estado_pago='APROBADO' THEN 1 ELSE 0 END)*100.0
/ NULLIFZERO(COUNT(*)) AS DECIMAL(5,2)) AS tasa_aprobacion_pct,
AVG(monto_pagado) AS ticket_prom,
SUM(monto_pagado) AS monto_total
FROM ecomm.pagos
GROUP BY 1, 2
ORDER BY metodo_pago, cuotas;
Q13 – Percentil 90 de tiempo de entrega por región y canal
SELECT
region, canal,
COUNT(*) AS pedidos_entregados,
AVG(tiempo_entrega) AS dias_prom,
MIN(tiempo_entrega) AS dias_min,
MAX(tiempo_entrega) AS dias_max,
PERCENTILE_CONT(0.50) WITHIN GROUP (ORDER BY tiempo_entrega) AS mediana,
PERCENTILE_CONT(0.90) WITHIN GROUP (ORDER BY tiempo_entrega) AS p90
FROM ecomm.pedidos
WHERE estado = 'ENTREGADO' AND tiempo_entrega > 0
GROUP BY 1, 2
ORDER BY p90 DESC;
Q14 – Canasta de productos: pares comprados juntos en el mismo mes
WITH compras AS (
SELECT cliente_id, producto_id,
EXTRACT(YEAR FROM fecha_pedido)*100
+ EXTRACT(MONTH FROM fecha_pedido) AS mes
FROM ecomm.pedidos WHERE estado <> 'CANCELADO'
GROUP BY 1,2,3
)
SELECT
a.producto_id AS prod_a, pa.nombre AS nombre_a,
b.producto_id AS prod_b, pb.nombre AS nombre_b,
COUNT(*) AS compras_juntas
FROM compras a
JOIN compras b
ON a.cliente_id = b.cliente_id
AND a.mes = b.mes
AND a.producto_id < b.producto_id
JOIN ecomm.productos pa ON a.producto_id = pa.producto_id
JOIN ecomm.productos pb ON b.producto_id = pb.producto_id
GROUP BY 1,2,3,4
HAVING compras_juntas >= 50
ORDER BY compras_juntas DESC;
Se agregarán las consultas Q15-Q25 cubriendo: detección de fraude por patrones de compra, forecast con regresión lineal en SQL, análisis de estacionalidad y segmentación geográfica avanzada.
Capítulo 13: Consultas Avanzadas – Fraude, Forecast y Estacionalidad
Este capítulo completa la serie de consultas analíticas sobre el dataset de e-commerce. Las consultas Q15 a Q25 abordan tres grandes áreas: detección de patrones anómalos y fraude, proyecciones de ventas con regresión lineal en SQL puro, y análisis de estacionalidad. Todas ejecutables directamente sobre las tablas del Capítulo 12.
SECCIÓN A — Detección de Fraude y Anomalías (Q15–Q18)
Q15 – Clientes con velocidad de compra anormal (burst de pedidos)
Detecta clientes que realizaron una cantidad inusualmente alta de pedidos en una ventana corta de tiempo (1 hora), un patrón típico de fraude automatizado o uso indebido de cuentas.
Q15 – Detección de ráfagas de pedidos en ventana de 1 hora
WITH pedidos_ts AS (
/* Agregar timestamp sintético combinando fecha + hora aleatoria */
/* En un entorno real, usar la columna timestamp del pedido */
SELECT
pedido_id,
cliente_id,
fecha_pedido,
monto_total,
canal,
-- Número de pedido del cliente en el día
ROW_NUMBER() OVER (
PARTITION BY cliente_id, fecha_pedido
ORDER BY pedido_id
) AS nro_pedido_dia,
-- Total de pedidos del cliente ese día
COUNT(*) OVER (
PARTITION BY cliente_id, fecha_pedido
) AS total_pedidos_dia,
-- Monto acumulado del cliente ese día
SUM(monto_total) OVER (
PARTITION BY cliente_id, fecha_pedido
ORDER BY pedido_id
ROWS UNBOUNDED PRECEDING
) AS monto_acum_dia
FROM ecomm.pedidos
WHERE estado <> 'CANCELADO'
),
estadisticas_globales AS (
SELECT
AVG(total_pedidos_dia) AS media_pedidos_dia,
STDDEV_POP(total_pedidos_dia) AS std_pedidos_dia
FROM (
SELECT cliente_id, fecha_pedido, COUNT(*) AS total_pedidos_dia
FROM ecomm.pedidos
GROUP BY cliente_id, fecha_pedido
) sub
)
SELECT DISTINCT
p.cliente_id,
c.nombre,
c.segmento,
p.fecha_pedido,
p.total_pedidos_dia,
p.monto_acum_dia,
eg.media_pedidos_dia,
eg.std_pedidos_dia,
/* Z-Score: cuántas desviaciones estándar sobre la media */
CAST(
(p.total_pedidos_dia - eg.media_pedidos_dia)
/ NULLIFZERO(eg.std_pedidos_dia)
AS DECIMAL(8,2)) AS z_score,
CASE
WHEN (p.total_pedidos_dia - eg.media_pedidos_dia)
/ NULLIFZERO(eg.std_pedidos_dia) > 3 THEN 'ALERTA_ALTA'
WHEN (p.total_pedidos_dia - eg.media_pedidos_dia)
/ NULLIFZERO(eg.std_pedidos_dia) > 2 THEN 'ALERTA_MEDIA'
ELSE 'NORMAL'
END AS nivel_alerta
FROM pedidos_ts p
CROSS JOIN estadisticas_globales eg
JOIN ecomm.clientes c ON p.cliente_id = c.cliente_id
WHERE p.total_pedidos_dia > eg.media_pedidos_dia + 2 * eg.std_pedidos_dia
ORDER BY z_score DESC, monto_acum_dia DESC;
Q16 – Detección de importes redondos sospechosos
En fraude financiero, los montos perfectamente redondos (ej: 10000.00, 50000.00) son indicadores de transacciones sintéticas. Esta consulta identifica concentraciones anómalas de importes redondos por cliente.
Q16 – Clientes con alta proporción de montos redondos
WITH analisis_redondos AS (
SELECT
cliente_id,
COUNT(*) AS total_pedidos,
/* Monto redondo: sin centavos y múltiplo de 100 */
SUM(CASE
WHEN MOD(CAST(monto_total AS INTEGER), 100) = 0
AND monto_total = CAST(monto_total AS INTEGER)
THEN 1 ELSE 0
END) AS pedidos_redondos,
SUM(monto_total) AS monto_total_cliente,
SUM(CASE
WHEN MOD(CAST(monto_total AS INTEGER), 100) = 0
AND monto_total = CAST(monto_total AS INTEGER)
THEN monto_total ELSE 0
END) AS monto_redondos
FROM ecomm.pedidos
WHERE estado <> 'CANCELADO'
GROUP BY cliente_id
HAVING total_pedidos >= 5 /* Solo clientes con historial suficiente */
)
SELECT
ar.cliente_id,
c.nombre,
c.segmento,
c.region,
total_pedidos,
pedidos_redondos,
monto_total_cliente,
monto_redondos,
CAST(pedidos_redondos * 100.0 / total_pedidos AS DECIMAL(5,2))
AS pct_redondos,
CAST(monto_redondos * 100.0 / NULLIFZERO(monto_total_cliente)
AS DECIMAL(5,2)) AS pct_monto_redondo
FROM analisis_redondos ar
JOIN ecomm.clientes c ON ar.cliente_id = c.cliente_id
WHERE pedidos_redondos * 100.0 / total_pedidos > 80 /* > 80% redondos */
ORDER BY pct_redondos DESC, monto_redondos DESC;
Q17 – Triangulación: pedidos entregados pero con pago rechazado
Uno de los patrones de fraude más comunes en e-commerce: el pedido llega a estado ENTREGADO pero el pago asociado fue rechazado o nunca se procesó correctamente.
Q17 – Pedidos entregados sin pago aprobado
SELECT
p.pedido_id,
p.cliente_id,
c.nombre,
c.segmento,
c.region,
p.fecha_pedido,
p.monto_total,
p.canal,
pg.estado_pago,
pg.metodo_pago,
pg.monto_pagado,
p.monto_total - COALESCE(pg.monto_pagado, 0) AS diferencia,
CASE
WHEN pg.pedido_id IS NULL THEN 'SIN_PAGO_REGISTRADO'
WHEN pg.estado_pago = 'RECHAZADO' THEN 'PAGO_RECHAZADO'
WHEN pg.estado_pago = 'PENDIENTE' THEN 'PAGO_PENDIENTE'
WHEN pg.monto_pagado < p.monto_total * 0.95
THEN 'PAGO_INSUFICIENTE'
END AS tipo_anomalia
FROM ecomm.pedidos p
JOIN ecomm.clientes c ON p.cliente_id = c.cliente_id
LEFT JOIN ecomm.pagos pg ON p.pedido_id = pg.pedido_id
WHERE p.estado = 'ENTREGADO'
AND (
pg.pedido_id IS NULL
OR pg.estado_pago IN ('RECHAZADO', 'PENDIENTE')
OR pg.monto_pagado < p.monto_total * 0.95
)
ORDER BY p.monto_total DESC;
Q18 – Score de riesgo compuesto por cliente
Consolida múltiples señales de riesgo en un score único por cliente, combinando: devoluciones excesivas, pagos rechazados, pedidos cancelados y comportamiento de compra atípico.
Q18 – Score de riesgo multicriterio
WITH metricas AS (
SELECT
p.cliente_id,
COUNT(*) AS total_ped,
SUM(CASE WHEN p.estado='CANCELADO' THEN 1 ELSE 0 END) AS cancelados,
SUM(CASE WHEN p.estado='ENTREGADO' THEN 1 ELSE 0 END) AS entregados,
COUNT(DISTINCT d.devolucion_id) AS devoluciones,
SUM(CASE WHEN pg.estado_pago='RECHAZADO'
THEN 1 ELSE 0 END) AS pagos_rechazados,
SUM(p.monto_total) AS monto_total
FROM ecomm.pedidos p
LEFT JOIN ecomm.devoluciones d ON p.pedido_id = d.pedido_id
LEFT JOIN ecomm.pagos pg ON p.pedido_id = pg.pedido_id
GROUP BY p.cliente_id
HAVING total_ped >= 3
),
scores AS (
SELECT
cliente_id, total_ped, monto_total,
cancelados, devoluciones, pagos_rechazados,
/* Cada señal suma puntos de riesgo (0-100) */
CAST(cancelados * 100.0 / NULLIFZERO(total_ped) AS DECIMAL(5,1)) AS pct_cancel,
CAST(devoluciones* 100.0 / NULLIFZERO(entregados) AS DECIMAL(5,1)) AS pct_devol,
CAST(pagos_rechazados*100.0/NULLIFZERO(total_ped) AS DECIMAL(5,1)) AS pct_rechaz,
/* Score ponderado: cancelaciones 40% + devoluciones 35% + rechazos 25% */
CAST(
(cancelados *100.0/NULLIFZERO(total_ped)) * 0.40 +
(devoluciones*100.0/NULLIFZERO(entregados)) * 0.35 +
(pagos_rechazados*100.0/NULLIFZERO(total_ped))* 0.25
AS DECIMAL(6,2)) AS risk_score
FROM metricas
)
SELECT
s.cliente_id,
c.nombre,
c.segmento,
c.region,
total_ped,
monto_total,
pct_cancel,
pct_devol,
pct_rechaz,
risk_score,
CASE
WHEN risk_score >= 40 THEN 'RIESGO_ALTO'
WHEN risk_score >= 20 THEN 'RIESGO_MEDIO'
ELSE 'RIESGO_BAJO'
END AS nivel_riesgo
FROM scores s
JOIN ecomm.clientes c ON s.cliente_id = c.cliente_id
ORDER BY risk_score DESC;
El score de riesgo es un modelo heurístico basado en reglas. En producción se combina con modelos de ML externalizados (Python/R) cuyos scores se cargan de vuelta a Teradata para enriquecimiento.
SECCIÓN B — Forecast con Regresión Lineal en SQL (Q19–Q22)
Teradata permite implementar regresión lineal simple directamente en SQL usando funciones de agregación estándar. Aunque no reemplaza a herramientas especializadas, es útil para forecasts rápidos sin salir del motor.
Q19 – Regresión lineal: tendencia de ventas mensuales
Calcula la recta de regresión Y = a + b*X sobre la serie mensual de ventas, donde X es el número de mes y Y es la facturación. Los coeficientes a (intercepto) y b (pendiente) permiten proyectar meses futuros.
Q19 – Coeficientes de regresión lineal sobre serie mensual
WITH serie_agrupada AS (
-- Paso 1: agrupar por periodo
SELECT
EXTRACT(YEAR FROM fecha_pedido)*100
+ EXTRACT(MONTH FROM fecha_pedido) AS periodo,
SUM(monto_total) AS y
FROM ecomm.pedidos
WHERE estado <> 'CANCELADO'
GROUP BY 1
),
serie AS (
-- Paso 2: asignar numero secuencial ya sin GROUP BY
SELECT
periodo,
y,
ROW_NUMBER() OVER (ORDER BY periodo) AS x
FROM serie_agrupada
),
regresion AS (
SELECT
COUNT(*) AS n,
AVG(x) AS media_x,
AVG(y) AS media_y,
CAST(
(SUM(x*y) - COUNT(*)*AVG(x)*AVG(y))
/ NULLIFZERO(SUM(x*x) - COUNT(*)*AVG(x)*AVG(x))
AS DECIMAL(18,4)) AS pendiente_b,
CAST(
AVG(y) -
(SUM(x*y) - COUNT(*)*AVG(x)*AVG(y))
/ NULLIFZERO(SUM(x*x) - COUNT(*)*AVG(x)*AVG(x))
* AVG(x)
AS DECIMAL(18,2)) AS intercepto_a
FROM serie
)
SELECT
s.periodo,
s.x,
s.y AS facturacion_real,
CAST(r.intercepto_a + r.pendiente_b * s.x
AS DECIMAL(18,2)) AS facturacion_ajustada,
r.pendiente_b,
r.intercepto_a,
CAST(ABS(s.y - (r.intercepto_a + r.pendiente_b*s.x))*100.0
/ NULLIFZERO(s.y) AS DECIMAL(6,2)) AS mape_pct
FROM serie s
CROSS JOIN regresion r
ORDER BY s.x;
Q20 – Proyección de ventas para los próximos 6 meses
Usando los coeficientes calculados en Q19, genera los valores proyectados para los 6 meses siguientes al último período con datos reales.
Q20 – Forecast 6 meses con intervalo de confianza simple
WITH serie_agrupada AS (
SELECT
EXTRACT(YEAR FROM fecha_pedido)*100
+ EXTRACT(MONTH FROM fecha_pedido) AS periodo,
SUM(monto_total) AS y
FROM ecomm.pedidos
WHERE estado <> 'CANCELADO'
GROUP BY 1
),
serie AS (
SELECT
periodo,
y,
ROW_NUMBER() OVER (ORDER BY periodo) AS x
FROM serie_agrupada
),
regresion AS (
SELECT
MAX(x) AS ultimo_x,
MAX(periodo) AS ultimo_periodo,
CAST(
(SUM(x*y) - COUNT(*)*AVG(x)*AVG(y))
/ NULLIFZERO(SUM(x*x) - COUNT(*)*AVG(x)*AVG(x))
AS DECIMAL(18,4)) AS b,
CAST(
AVG(y) -
(SUM(x*y) - COUNT(*)*AVG(x)*AVG(y))
/ NULLIFZERO(SUM(x*x) - COUNT(*)*AVG(x)*AVG(x))
* AVG(x)
AS DECIMAL(18,2)) AS a,
STDDEV_POP(y) AS std_y
FROM serie
),
futuros AS (
SELECT 1 AS offset FROM (SELECT 1 AS d) t
UNION ALL SELECT 2 FROM (SELECT 1 AS d) t
UNION ALL SELECT 3 FROM (SELECT 1 AS d) t
UNION ALL SELECT 4 FROM (SELECT 1 AS d) t
UNION ALL SELECT 5 FROM (SELECT 1 AS d) t
UNION ALL SELECT 6 FROM (SELECT 1 AS d) t
)
SELECT
r.ultimo_x + f.offset AS x_futuro,
/* Calcular YYYYMM del periodo proyectado */
EXTRACT(YEAR FROM ADD_MONTHS(
CAST(CAST(r.ultimo_periodo AS CHAR(6)) || '01'
AS DATE FORMAT 'YYYYMMDD'),
f.offset)) * 100
+ EXTRACT(MONTH FROM ADD_MONTHS(
CAST(CAST(r.ultimo_periodo AS CHAR(6)) || '01'
AS DATE FORMAT 'YYYYMMDD'),
f.offset)) AS periodo_proyectado,
CAST(r.a + r.b*(r.ultimo_x + f.offset) AS DECIMAL(18,2)) AS forecast,
CAST(r.a + r.b*(r.ultimo_x+f.offset) - 1.645*r.std_y
AS DECIMAL(18,2)) AS limite_inferior_90,
CAST(r.a + r.b*(r.ultimo_x+f.offset) + 1.645*r.std_y
AS DECIMAL(18,2)) AS limite_superior_90,
r.b AS pendiente_mensual,
CAST(r.b*100.0 / NULLIFZERO(r.a) AS DECIMAL(6,2)) AS crecimiento_pct_mensual
FROM futuros f
CROSS JOIN regresion r
ORDER BY f.offset;
Q21 – Regresión por categoría: ¿cuáles crecen más rápido?
Aplica la misma regresión lineal a cada categoría de productos en paralelo, usando PARTITION BY, para identificar cuáles tienen mayor pendiente de crecimiento.
Q21 – Pendiente de crecimiento por categoría (REGR_SLOPE)
/* Teradata soporta funciones de regresión ANSI directamente */
WITH serie_agrupada AS (
-- Paso 1: agregar monto por categoria y periodo
SELECT
pr.categoria,
EXTRACT(YEAR FROM p.fecha_pedido)*100
+ EXTRACT(MONTH FROM p.fecha_pedido) AS periodo,
SUM(p.monto_total) AS y
FROM ecomm.pedidos p
JOIN ecomm.productos pr ON p.producto_id = pr.producto_id
WHERE p.estado <> 'CANCELADO'
GROUP BY 1, 2
),
serie_cat AS (
-- Paso 2: asignar numero de mes secuencial por categoria
SELECT
categoria,
periodo,
y,
ROW_NUMBER() OVER (
PARTITION BY categoria
ORDER BY periodo
) AS x
FROM serie_agrupada
)
SELECT
categoria,
COUNT(*) AS meses_con_datos,
AVG(y) AS facturacion_prom_mensual,
CAST(REGR_SLOPE(y, x) AS DECIMAL(18,2)) AS pendiente_mensual,
CAST(REGR_INTERCEPT(y, x) AS DECIMAL(18,2)) AS intercepto,
CAST(REGR_R2(y, x) AS DECIMAL(8,4)) AS r_cuadrado,
CAST(REGR_SLOPE(y, x) * 100.0
/ NULLIFZERO(AVG(y)) AS DECIMAL(6,2)) AS crecim_pct_mensual
FROM serie_cat
GROUP BY categoria
ORDER BY pendiente_mensual DESC;
REGR_SLOPE(Y,X), REGR_INTERCEPT(Y,X) y REGR_R2(Y,X) son funciones de regresión incluidas en el estándar SQL:2003 y disponibles en Teradata sin necesidad de código adicional. Mucho más concisas que el cálculo manual de Q19.
Q22 – Forecast con descomposición de tendencia y ajuste estacional
Mejora el forecast puro de regresión agregando un índice estacional mensual. Primero calcula el promedio de cada mes del año, luego ajusta la proyección por ese factor.
Q22 – Forecast con ajuste estacional (trend × índice_mes)
WITH mensual_agrupado AS (
SELECT
EXTRACT(YEAR FROM fecha_pedido) AS anio,
EXTRACT(MONTH FROM fecha_pedido) AS mes,
EXTRACT(YEAR FROM fecha_pedido)*100
+ EXTRACT(MONTH FROM fecha_pedido) AS periodo,
SUM(monto_total) AS y
FROM ecomm.pedidos
WHERE estado <> 'CANCELADO'
GROUP BY 1, 2
),
mensual AS (
SELECT
anio, mes, periodo, y,
ROW_NUMBER() OVER (ORDER BY periodo) AS x
FROM mensual_agrupado
),
estacional AS (
SELECT
mes,
AVG(y) AS prom_mes,
CAST(AVG(y) / NULLIFZERO(SUM(AVG(y)) OVER ())
AS DECIMAL(8,4)) AS indice_estacional
FROM mensual
GROUP BY mes
),
regresion AS (
SELECT
CAST(REGR_SLOPE(y,x) AS DECIMAL(18,4)) AS b,
CAST(REGR_INTERCEPT(y,x) AS DECIMAL(18,2)) AS a,
MAX(x) AS ultimo_x,
MAX(periodo) AS ultimo_periodo
FROM mensual
),
futuros AS (
SELECT 1 AS n FROM (SELECT 1 AS d) t
UNION ALL SELECT 2 FROM (SELECT 1 AS d) t
UNION ALL SELECT 3 FROM (SELECT 1 AS d) t
UNION ALL SELECT 4 FROM (SELECT 1 AS d) t
UNION ALL SELECT 5 FROM (SELECT 1 AS d) t
UNION ALL SELECT 6 FROM (SELECT 1 AS d) t
)
SELECT
r.ultimo_x + f.n AS x,
EXTRACT(YEAR FROM ADD_MONTHS(
CAST(CAST(r.ultimo_periodo AS CHAR(6)) || '01'
AS DATE FORMAT 'YYYYMMDD'), f.n)) * 100
+ EXTRACT(MONTH FROM ADD_MONTHS(
CAST(CAST(r.ultimo_periodo AS CHAR(6)) || '01'
AS DATE FORMAT 'YYYYMMDD'), f.n)) AS periodo,
CAST(r.a + r.b*(r.ultimo_x+f.n) AS DECIMAL(18,2)) AS trend_puro,
e.indice_estacional,
CAST((r.a + r.b*(r.ultimo_x+f.n)) * e.indice_estacional
AS DECIMAL(18,2)) AS forecast_ajustado
FROM futuros f
CROSS JOIN regresion r
JOIN estacional e
ON e.mes = EXTRACT(MONTH FROM ADD_MONTHS(
CAST(CAST(r.ultimo_periodo AS CHAR(6)) || '01'
AS DATE FORMAT 'YYYYMMDD'), f.n))
ORDER BY f.n;
SECCIÓN C — Análisis de Estacionalidad (Q23–Q25)
Q23 – Índices estacionales por mes y día de semana
Calcula dos dimensiones de estacionalidad simultáneamente: el índice mensual (enero vs diciembre) y el índice por día de la semana (lunes vs domingo). Útil para planificación de stock y campañas.
Q23 – Doble índice estacional: mensual y semanal
/* ── Índice Estacional MENSUAL ── */
WITH por_mes AS (
SELECT
EXTRACT(MONTH FROM fecha_pedido) AS mes_num,
CASE EXTRACT(MONTH FROM fecha_pedido)
WHEN 1 THEN 'Enero' WHEN 2 THEN 'Febrero'
WHEN 3 THEN 'Marzo' WHEN 4 THEN 'Abril'
WHEN 5 THEN 'Mayo' WHEN 6 THEN 'Junio'
WHEN 7 THEN 'Julio' WHEN 8 THEN 'Agosto'
WHEN 9 THEN 'Septiembre' WHEN 10 THEN 'Octubre'
WHEN 11 THEN 'Noviembre' WHEN 12 THEN 'Diciembre'
END AS mes_nombre,
AVG(monto_total) AS ticket_prom_mes,
COUNT(*) AS pedidos_prom_mes
FROM ecomm.pedidos
WHERE estado <> 'CANCELADO'
GROUP BY 1, 2
)
SELECT
mes_num, mes_nombre,
ticket_prom_mes,
pedidos_prom_mes,
/* Índice > 1.0 = mes por encima del promedio anual */
CAST(ticket_prom_mes / AVG(ticket_prom_mes) OVER ()
AS DECIMAL(6,4)) AS indice_ticket,
CAST(pedidos_prom_mes / AVG(pedidos_prom_mes) OVER ()
AS DECIMAL(6,4)) AS indice_volumen
FROM por_mes
ORDER BY mes_num;
Q23b – Índice estacional por día de la semana
WITH por_dia AS (
SELECT
TD_DAY_OF_WEEK(fecha_pedido) AS dia_num, /* 0=Dom..6=Sab */
CASE TD_DAY_OF_WEEK(fecha_pedido)
WHEN 0 THEN 'Domingo' WHEN 1 THEN 'Lunes'
WHEN 2 THEN 'Martes' WHEN 3 THEN 'Miércoles'
WHEN 4 THEN 'Jueves' WHEN 5 THEN 'Viernes'
WHEN 6 THEN 'Sábado'
END AS dia_nombre,
COUNT(*) AS total_pedidos,
SUM(monto_total) AS monto_total,
AVG(monto_total) AS ticket_prom
FROM ecomm.pedidos
WHERE estado <> 'CANCELADO'
GROUP BY 1, 2
)
SELECT
dia_num, dia_nombre,
total_pedidos, monto_total, ticket_prom,
CAST(total_pedidos / AVG(total_pedidos) OVER ()
AS DECIMAL(6,4)) AS indice_volumen_dia,
CAST(ticket_prom / AVG(ticket_prom) OVER ()
AS DECIMAL(6,4)) AS indice_ticket_dia
FROM por_dia
ORDER BY indice_volumen_dia DESC;
Q24 – Mapa de calor: facturación por mes y día de semana (pivot)
Genera una tabla pivoteada que muestra la facturación promedio en la intersección de cada mes con cada día de la semana, creando un mapa de calor que revela los patrones de compra bidimensionales.
Q24 – Pivot: facturación promedio por MES x DÍA DE SEMANA
SELECT
CASE EXTRACT(MONTH FROM fecha_pedido)
WHEN 1 THEN 'Ene' WHEN 2 THEN 'Feb' WHEN 3 THEN 'Mar'
WHEN 4 THEN 'Abr' WHEN 5 THEN 'May' WHEN 6 THEN 'Jun'
WHEN 7 THEN 'Jul' WHEN 8 THEN 'Ago' WHEN 9 THEN 'Sep'
WHEN 10 THEN 'Oct' WHEN 11 THEN 'Nov' WHEN 12 THEN 'Dic'
END AS mes,
EXTRACT(MONTH FROM fecha_pedido) AS mes_num,
/* Una columna por día de semana */
CAST(AVG(CASE WHEN TD_DAY_OF_WEEK(fecha_pedido)=1
THEN monto_total END) AS DECIMAL(12,0)) AS lunes,
CAST(AVG(CASE WHEN TD_DAY_OF_WEEK(fecha_pedido)=2
THEN monto_total END) AS DECIMAL(12,0)) AS martes,
CAST(AVG(CASE WHEN TD_DAY_OF_WEEK(fecha_pedido)=3
THEN monto_total END) AS DECIMAL(12,0)) AS miercoles,
CAST(AVG(CASE WHEN TD_DAY_OF_WEEK(fecha_pedido)=4
THEN monto_total END) AS DECIMAL(12,0)) AS jueves,
CAST(AVG(CASE WHEN TD_DAY_OF_WEEK(fecha_pedido)=5
THEN monto_total END) AS DECIMAL(12,0)) AS viernes,
CAST(AVG(CASE WHEN TD_DAY_OF_WEEK(fecha_pedido)=6
THEN monto_total END) AS DECIMAL(12,0)) AS sabado,
CAST(AVG(CASE WHEN TD_DAY_OF_WEEK(fecha_pedido)=0
THEN monto_total END) AS DECIMAL(12,0)) AS domingo
FROM ecomm.pedidos
WHERE estado <> 'CANCELADO'
GROUP BY mes, mes_num
ORDER BY mes_num;
Q25 – Análisis de estacionalidad multi-año con descomposición STL manual
Descompone la serie temporal en tres componentes: tendencia (suavizado de media móvil 12 meses), estacionalidad (patrón repetitivo), y residuo (ruido). Esta es la base del método STL (Seasonal and Trend decomposition using Loess) implementada en SQL puro.
Q25 – Descomposición STL manual: tendencia + estacionalidad + residuo
WITH mensual AS (
SELECT
EXTRACT(YEAR FROM fecha_pedido) AS anio,
EXTRACT(MONTH FROM fecha_pedido) AS mes,
SUM(monto_total) AS y
FROM ecomm.pedidos
WHERE estado <> 'CANCELADO'
GROUP BY 1, 2
),
con_tendencia AS (
SELECT
anio, mes, y,
/* Tendencia = media móvil centrada de 12 meses (suaviza ciclo anual) */
AVG(y) OVER (
ORDER BY anio, mes
ROWS BETWEEN 5 PRECEDING AND 6 FOLLOWING
) AS tendencia_mm12
FROM mensual
),
/* Componente estacional = y desestacionalizado (y / tendencia) */
con_desestac AS (
SELECT
anio, mes, y, tendencia_mm12,
CAST(y / NULLIFZERO(tendencia_mm12) AS DECIMAL(8,4)) AS y_desestac
FROM con_tendencia
WHERE tendencia_mm12 IS NOT NULL
),
/* Índice estacional = promedio del ratio por mes (patrón anual) */
indice_estacional AS (
SELECT
mes,
CAST(AVG(y_desestac) AS DECIMAL(8,4)) AS indice_est
FROM con_desestac
GROUP BY mes
)
/* Resultado final: serie descompuesta */
SELECT
d.anio,
d.mes,
d.y AS serie_original,
CAST(d.tendencia_mm12 AS DECIMAL(18,2)) AS tendencia,
i.indice_est AS indice_estacional,
/* Componente estacional absoluta */
CAST(d.tendencia_mm12 * i.indice_est
AS DECIMAL(18,2)) AS componente_estacional,
/* Residuo = lo que no explican tendencia ni estacionalidad */
CAST(d.y - d.tendencia_mm12 * i.indice_est
AS DECIMAL(18,2)) AS residuo,
/* Residuo % sobre la serie original */
CAST((d.y - d.tendencia_mm12 * i.indice_est)
* 100.0 / NULLIFZERO(d.y)
AS DECIMAL(6,2)) AS residuo_pct
FROM con_desestac d
JOIN indice_estacional i ON d.mes = i.mes
ORDER BY d.anio, d.mes;
Residuos con valor absoluto > 15-20% indican eventos atípicos (promociones, cortes de stock, problemas operativos). Un indice_estacional de 1.30 para diciembre significa que ese mes vende 30% más que el promedio anual ajustado por tendencia.
Resumen: Catálogo Completo de Consultas Q01–Q25
| N° | Consulta | Técnicas Usadas | Nivel |
|---|---|---|---|
| Q01 | Resumen general del dataset | COUNT, SUM, AVG, MIN, MAX | Básico |
| Q02 | Ventas por canal y estado | GROUP BY, agregaciones | Básico |
| Q03 | Top categorías con margen estimado | JOIN, NULLIFZERO, cálculo de margen | Básico |
| Q04 | Evolución mensual con variación % | LAG, SUM acumulado, EXTRACT | Intermedio |
| Q05 | Top 3 productos por categoría | RANK, PARTITION BY, pct. participación | Intermedio |
| Q06 | Media móvil 7 días y anomalías | AVG/STDDEV ventana deslizante, CASE | Intermedio |
| Q07 | Segmentación RFM | NTILE, lógica de scoring multicriteria | Intermedio |
| Q08 | Análisis de cohortes | FIRST_VALUE, GROUP BY temporal | Avanzado |
| Q09 | Detección de churn (GOLD/PLATINUM) | SUM condicional, comparación períodos | Avanzado |
| Q10 | Tasa de devolución por cat. y región | LEFT JOIN, NULLIFZERO, tasa calculada | Avanzado |
| Q11 | LTV proyectado 12 meses | MONTHS_BETWEEN, proyección por segmento | Avanzado |
| Q12 | Métodos de pago y aprobación | SUM CASE, tasa de aprobación | Intermedio |
| Q13 | Percentil 90 de tiempo de entrega | PERCENTILE_CONT, agrupación multi-dim | Avanzado |
| Q14 | Canasta de productos (Market Basket) | Self-JOIN, HAVING, análisis de pares | Avanzado |
| Q15 | Detección de ráfagas de pedidos (fraude) | ROW_NUMBER, CROSS JOIN, Z-Score | Expert |
| Q16 | Montos redondos sospechosos | MOD, CAST, detección por umbral | Expert |
| Q17 | Entregados sin pago aprobado | LEFT JOIN, COALESCE, triangulación | Expert |
| Q18 | Score de riesgo compuesto | Score ponderado multi-CTE | Expert |
| Q19 | Regresión lineal manual en SQL | ROW_NUMBER, fórmula b/a, CROSS JOIN | Expert |
| Q20 | Forecast 6 meses con IC del 90% | Proyección + STDDEV, límites CI | Expert |
| Q21 | Pendiente de crecimiento por categoría | REGR_SLOPE, REGR_R2, REGR_INTERCEPT | Expert |
| Q22 | Forecast con ajuste estacional | ADD_MONTHS, índice estacional, trend×est. | Expert |
| Q23 | Índices estacionales mes y día semana | TD_DAY_OF_WEEK, doble índice | Expert |
| Q24 | Mapa de calor mes × día semana (pivot) | CASE pivot, AVG condicional | Expert |
| Q25 | Descomposición STL: tend+estac+residuo | MM12, índice estacional, residuo % | Expert |
Bibliografia y Referencias
Este manual fue construido tomando como base la documentacion oficial de Teradata, bibliografia academica de bases de datos relacionales y fuentes de referencia de la industria. A continuacion se detallan todas las fuentes utilizadas.
Documentacion Oficial Teradata
Toda la documentacion oficial se encuentra disponible en docs.teradata.com
Teradata Database SQL Reference. Referencia completa del lenguaje SQL de Teradata: funciones analiticas, tipos de datos, sintaxis DDL/DML y extensiones propietarias del motor.
Teradata FastLoad Reference Manual. Guia oficial de FastLoad: sintaxis del DEFINE, fases de carga (Acquisition y Application), manejo de tablas de error y opciones de sesion.
Teradata MultiLoad Reference Manual. Guia oficial de MultiLoad: layouts, operaciones DML masivas (INSERT/UPDATE/DELETE), work tables, error tables y checkpoints de restart.
Teradata Parallel Transporter User Guide. Documentacion de TPT como marco unificado moderno que reemplaza a FastLoad y MultiLoad a partir de Teradata 13.x.
Teradata Database Design. Guia de diseno: eleccion del Indice Primario (PI), PPI, distribucion de datos entre AMPs y estrategias de optimizacion.
Teradata Database Administration. Administracion del sistema: usuarios, roles, perfiles, espacios PERM/SPOOL/TEMP, monitoreo y vistas del diccionario DBC.
DBC Views Reference. Referencia de las vistas del diccionario de datos del sistema (DBC.TablesV, DBC.IndicesV, DBC.SessionInfoV, etc.).
Bibliografia Academica
Silberschatz, A.; Korth, H.; Sudarshan, S. Database System Concepts, 7ma edicion. McGraw-Hill Education, 2019. ISBN: 978-0078022159.
Silberschatz, A.; Korth, H.; Sudarshan, S. Fundamentos de Bases de Datos, 6ta edicion en espanol. McGraw-Hill / Interamericana, 2014. ISBN: 978-8448190330.
Kimball, R.; Ross, M. The Data Warehouse Toolkit, 3ra edicion. Wiley, 2013. ISBN: 978-1118530801. Referencia para modelado dimensional y esquemas estrella del Capitulo 12.
Inmon, W.H. Building the Data Warehouse, 4ta edicion. Wiley, 2005. ISBN: 978-0764599446. Fundamentos de arquitectura de Data Warehouse empresarial.
Ramakrishnan, R.; Gehrke, J. Database Management Systems, 3ra edicion. McGraw-Hill, 2002. ISBN: 978-0072465631.
Fuentes de Datos Utilizadas
Catálogo de Precios de Referencia de Supermercados Argentina. Archivo de precios de lista de productos de gondola del mercado argentino (~707.000 registros) con descripciones, marcas y unidades de medida. Utilizado en el Capitulo 12 para generar la tabla ecomm.productos con datos reales del mercado local.
Dataset sintetico e-commerce ecomm.*. Generado con el script generar_dataset_ecomm.py. Produce ~2.000.000 de registros en 4 tablas (clientes, productos, pedidos, pagos). Base de todos los ejercicios Q01-Q25.
Herramientas y Tecnologias
Teradata Database 17.x. Motor de base de datos sobre el que se desarrollaron y validaron todos los ejemplos SQL del manual.
Teradata Studio 17.x. Entorno de desarrollo y ejecucion de consultas SQL utilizado durante la construccion del manual.
Teradata Tools and Utilities (TTU). Conjunto de utilitarios que incluye FastLoad, MultiLoad y BTEQ. Los scripts de FastLoad fueron validados en un entorno Teradata 17 real.
Python 3.8+. Lenguaje utilizado para la generacion del dataset sintetico de e-commerce. Solo se utilizaron modulos de la biblioteca estandar.
Claude — Anthropic, 2025. Asistente de inteligencia artificial utilizado como co-autor en la redaccion, estructuracion y generacion de ejemplos del manual.
Nota sobre la Autoria
Este manual fue construido de forma colaborativa entre Javier, profesional de datos con experiencia avanzada en entornos Teradata, DataStage y Control-M, y Claude, el asistente de inteligencia artificial de Anthropic. La estructura, los contenidos tecnicos, los ejercicios practicos y el dataset de practica son el resultado de un proceso iterativo de construccion conjunta.
Todos los ejemplos de SQL fueron revisados para garantizar su compatibilidad con la sintaxis de Teradata. Los scripts de FastLoad fueron validados y corregidos directamente en un entorno Teradata 17 real durante la construccion del manual.
Los precios del catalogo de productos argentino corresponden a valores de lista de referencia y pueden no reflejar precios actuales de mercado. Su uso en este manual es exclusivamente educativo y de practica de consultas SQL.
Archivos de práctica
Los scripts y recursos de este manual, para descargar y practicar en tu propio entorno.