📘 Laboratorio 1: Análisis de Datos

🎯 Objetivo

Aplicar técnicas avanzadas de DML (agregaciones, funciones de ventana, limpieza de datos, subconsultas, CTEs) al caso del asistente virtual Aletza, preparando datos para análisis y consumo por modelos de IA.


📋 Requisitos Previos

  • Tener ejecutado el Laboratorio 1 con todas las tablas creadas y datos insertados
  • Conectado a Supabase con el SQL Editor abierto

🔍 Paso 1: Agregaciones y Agrupamientos (GROUP BY)

1.1 Funciones de Agregación Básicas

-- Contar total de usuarios
SELECT COUNT(*) AS total_usuarios FROM usuarios;

-- Contar usuarios con país registrado
SELECT COUNT(pais) AS usuarios_con_pais FROM usuarios;

-- Contar países distintos
SELECT COUNT(DISTINCT pais) AS paises_distintos FROM usuarios WHERE pais IS NOT NULL;

-- Suma total de muestras de voz
SELECT SUM(muestras_tomadas) AS total_muestras FROM perfiles_voz;

-- Promedio de tiempo de ejecución de módulos
SELECT AVG(tiempo_ejecucion_ms) AS tiempo_promedio_ms FROM logs_ejecucion;

-- Máximo y mínimo tiempo de ejecución
SELECT 
    MAX(tiempo_ejecucion_ms) AS tiempo_maximo,
    MIN(tiempo_ejecucion_ms) AS tiempo_minimo
FROM logs_ejecucion;

1.2 GROUP BY Básico

-- Contar usuarios por país
SELECT 
    pais, 
    COUNT(*) AS cantidad_usuarios
FROM usuarios
WHERE pais IS NOT NULL
GROUP BY pais
ORDER BY cantidad_usuarios DESC;

-- Contar mensajes por modalidad
SELECT 
    modalidad,
    COUNT(*) AS cantidad_mensajes
FROM mensajes
GROUP BY modalidad
ORDER BY cantidad_mensajes DESC;

-- Sesiones por canal
SELECT 
    canal,
    COUNT(*) AS total_sesiones
FROM sesiones
GROUP BY canal
ORDER BY total_sesiones DESC;

-- Tasa de éxito por módulo
SELECT 
    modulo,
    COUNT(*) AS total_ejecuciones,
    SUM(CASE WHEN exito THEN 1 ELSE 0 END) AS exitosas,
    ROUND(100.0 * SUM(CASE WHEN exito THEN 1 ELSE 0 END) / COUNT(*), 2) AS tasa_exito
FROM logs_ejecucion
GROUP BY modulo
ORDER BY tasa_exito DESC;

1.3 GROUP BY con Múltiples Columnas

-- Mensajes por usuario y modalidad
SELECT 
    u.username,
    m.modalidad,
    COUNT(*) AS cantidad
FROM mensajes m
INNER JOIN usuarios u ON m.remitente_id = u.id
GROUP BY u.username, m.modalidad
ORDER BY u.username, cantidad DESC;

-- Sesiones por usuario y canal
SELECT 
    u.username,
    s.canal,
    COUNT(*) AS sesiones
FROM sesiones s
INNER JOIN usuarios u ON s.usuario_id = u.id
GROUP BY u.username, s.canal
ORDER BY u.username, sesiones DESC;

1.4 HAVING (Filtrar Grupos)

-- Usuarios con más de 1 sesión
SELECT 
    u.username,
    COUNT(s.id) AS total_sesiones
FROM usuarios u
LEFT JOIN sesiones s ON u.id = s.usuario_id
GROUP BY u.id, u.username
HAVING COUNT(s.id) > 1
ORDER BY total_sesiones DESC;

-- Módulos con tasa de éxito menor a 95%
SELECT 
    modulo,
    COUNT(*) AS total,
    ROUND(100.0 * SUM(CASE WHEN exito THEN 1 ELSE 0 END) / COUNT(*), 2) AS tasa_exito
FROM logs_ejecucion
GROUP BY modulo
HAVING ROUND(100.0 * SUM(CASE WHEN exito THEN 1 ELSE 0 END) / COUNT(*), 2) < 95;

-- Países con más de 2 usuarios
SELECT 
    pais,
    COUNT(*) AS cantidad
FROM usuarios
WHERE pais IS NOT NULL
GROUP BY pais
HAVING COUNT(*) > 2;

1.5 ROLLUP, CUBE y GROUPING SETS

-- ROLLUP: Subtotales por país y ciudad
SELECT 
    pais,
    ciudad,
    COUNT(*) AS cantidad
FROM usuarios
WHERE pais IS NOT NULL
GROUP BY ROLLUP (pais, ciudad)
ORDER BY pais NULLS LAST, ciudad NULLS LAST;

-- CUBE: Todas las combinaciones
SELECT 
    modalidad,
    EXTRACT(YEAR FROM enviado_en) AS año,
    COUNT(*) AS cantidad
FROM mensajes
GROUP BY CUBE (modalidad, EXTRACT(YEAR FROM enviado_en))
ORDER BY modalidad, año;

-- GROUPING SETS: Combinaciones específicas
SELECT 
    modulo,
    EXTRACT(YEAR FROM ejecutado_en) AS año,
    COUNT(*) AS total,
    ROUND(AVG(tiempo_ejecucion_ms), 2) AS tiempo_promedio
FROM logs_ejecucion
GROUP BY GROUPING SETS (
    (modulo),
    (EXTRACT(YEAR FROM ejecutado_en)),
    (modulo, EXTRACT(YEAR FROM ejecutado_en)),
    ()
)
ORDER BY modulo NULLS LAST, año NULLS LAST;

📊 Paso 2: Funciones de Ventana (Window Functions)

2.1 Funciones de Ranking

-- Ranking de usuarios por cantidad de mensajes
SELECT 
    u.username,
    COUNT(m.id) AS mensajes,
    ROW_NUMBER() OVER (ORDER BY COUNT(m.id) DESC) AS row_num,
    RANK() OVER (ORDER BY COUNT(m.id) DESC) AS rank,
    DENSE_RANK() OVER (ORDER BY COUNT(m.id) DESC) AS dense_rank,
    NTILE(3) OVER (ORDER BY COUNT(m.id) DESC) AS tercil
FROM usuarios u
LEFT JOIN mensajes m ON u.id = m.remitente_id
GROUP BY u.id, u.username
ORDER BY mensajes DESC;

-- Top 3 módulos por tiempo de ejecución (por partición de éxito)
SELECT 
    modulo,
    tiempo_ejecucion_ms,
    exito,
    ROW_NUMBER() OVER (PARTITION BY exito ORDER BY tiempo_ejecucion_ms DESC) AS ranking
FROM logs_ejecucion
WHERE tiempo_ejecucion_ms IS NOT NULL
ORDER BY exito DESC, ranking;

2.2 Funciones de Valor (LAG, LEAD)

-- Comparar mensajes consecutivos de un usuario
SELECT 
    u.username,
    m.contenido,
    m.enviado_en,
    LAG(m.contenido, 1) OVER (PARTITION BY m.remitente_id ORDER BY m.enviado_en) AS mensaje_anterior,
    LEAD(m.contenido, 1) OVER (PARTITION BY m.remitente_id ORDER BY m.enviado_en) AS mensaje_siguiente,
    EXTRACT(EPOCH FROM (m.enviado_en - LAG(m.enviado_en, 1) OVER (PARTITION BY m.remitente_id ORDER BY m.enviado_en))) / 60 AS minutos_diferencia
FROM mensajes m
INNER JOIN usuarios u ON m.remitente_id = u.id
ORDER BY u.username, m.enviado_en;

-- Tiempo entre sesiones de un usuario
SELECT 
    u.username,
    s.iniciada_en,
    LAG(s.iniciada_en, 1) OVER (PARTITION BY s.usuario_id ORDER BY s.iniciada_en) AS sesion_anterior,
    EXTRACT(EPOCH FROM (s.iniciada_en - LAG(s.iniciada_en, 1) OVER (PARTITION BY s.usuario_id ORDER BY s.iniciada_en))) / 3600 AS horas_diferencia
FROM sesiones s
INNER JOIN usuarios u ON s.usuario_id = u.id
ORDER BY u.username, s.iniciada_en;

2.3 Funciones de Agregación como Ventana

-- Acumulado de mensajes por día
SELECT 
    DATE(enviado_en) AS fecha,
    COUNT(*) AS mensajes_dia,
    SUM(COUNT(*)) OVER (ORDER BY DATE(enviado_en)) AS acumulado_mensajes
FROM mensajes
GROUP BY DATE(enviado_en)
ORDER BY fecha;

-- Media móvil de 3 días de mensajes
SELECT 
    DATE(enviado_en) AS fecha,
    COUNT(*) AS mensajes_dia,
    AVG(COUNT(*)) OVER (ORDER BY DATE(enviado_en) ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS media_movil_3dias
FROM mensajes
GROUP BY DATE(enviado_en)
ORDER BY fecha;

-- Porcentaje del total de mensajes por usuario
SELECT 
    u.username,
    COUNT(m.id) AS mensajes,
    ROUND(100.0 * COUNT(m.id) / SUM(COUNT(m.id)) OVER (), 2) AS porcentaje_total
FROM usuarios u
LEFT JOIN mensajes m ON u.id = m.remitente_id
GROUP BY u.id, u.username
ORDER BY mensajes DESC;