# Optimización SQL — Patrones avanzados

## CTEs (Common Table Expressions)

```sql
-- Mejorar legibilidad de queries complejos
WITH facturas_vencidas AS (
    SELECT id, cliente_id, monto_total, fecha_vencimiento
    FROM facturas
    WHERE estatus = 'emitida'
      AND fecha_vencimiento < CURRENT_DATE
),
clientes_deudores AS (
    SELECT
        cliente_id,
        COUNT(*) AS num_facturas_vencidas,
        SUM(monto_total) AS total_deuda
    FROM facturas_vencidas
    GROUP BY cliente_id
    HAVING SUM(monto_total) > 10000
)
SELECT
    c.nombre,
    c.email,
    cd.num_facturas_vencidas,
    cd.total_deuda
FROM clientes_deudores cd
JOIN clientes c ON c.id = cd.cliente_id
ORDER BY cd.total_deuda DESC;

-- CTE recursivo para jerarquías
WITH RECURSIVE categoria_arbol AS (
    -- Base: categorías raíz
    SELECT id, nombre, padre_id, 0 AS nivel
    FROM categorias
    WHERE padre_id IS NULL

    UNION ALL

    -- Recursión: hijos
    SELECT c.id, c.nombre, c.padre_id, ca.nivel + 1
    FROM categorias c
    JOIN categoria_arbol ca ON c.padre_id = ca.id
)
SELECT * FROM categoria_arbol ORDER BY nivel, nombre;
```

---

## Window Functions

```sql
-- Ranking de facturas por cliente
SELECT
    f.id,
    f.folio,
    c.nombre AS cliente,
    f.monto_total,
    RANK() OVER (
        PARTITION BY f.cliente_id
        ORDER BY f.monto_total DESC
    ) AS rank_por_monto,
    SUM(f.monto_total) OVER (
        PARTITION BY f.cliente_id
        ORDER BY f.fecha
        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
    ) AS acumulado_cliente
FROM facturas f
JOIN clientes c ON f.cliente_id = c.id
WHERE f.estatus = 'emitida';

-- Funciones de ventana más útiles:
-- ROW_NUMBER() — número de fila sin empates
-- RANK()       — ranking con huecos en empates
-- DENSE_RANK() — ranking sin huecos
-- LAG(col, n)  — valor de n filas anteriores
-- LEAD(col, n) — valor de n filas siguientes
-- FIRST_VALUE(col) / LAST_VALUE(col) — primer/último valor en la ventana
```

---

## Particionamiento de tablas

```sql
-- Particionar tabla grande por rango de fecha
CREATE TABLE facturas (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    fecha DATE NOT NULL,
    empresa_id UUID NOT NULL,
    monto_total DECIMAL(12,2),
    estatus TEXT
) PARTITION BY RANGE (fecha);

-- Crear particiones por año/trimestre
CREATE TABLE facturas_2025 PARTITION OF facturas
    FOR VALUES FROM ('2025-01-01') TO ('2026-01-01');

CREATE TABLE facturas_2026 PARTITION OF facturas
    FOR VALUES FROM ('2026-01-01') TO ('2027-01-01');

-- Índices en partición — se heredan automáticamente en PostgreSQL 11+
CREATE INDEX ON facturas(empresa_id, fecha);
```

---

## Vistas materializadas

```sql
-- Vista materializada para reportes costosos
CREATE MATERIALIZED VIEW resumen_ventas_mensuales AS
SELECT
    DATE_TRUNC('month', f.fecha) AS mes,
    e.nombre AS empresa,
    COUNT(*) AS total_facturas,
    SUM(f.monto_total) AS total_ventas,
    AVG(f.monto_total) AS promedio_factura
FROM facturas f
JOIN empresas e ON f.empresa_id = e.id
WHERE f.estatus = 'emitida'
GROUP BY DATE_TRUNC('month', f.fecha), e.nombre
ORDER BY mes DESC, total_ventas DESC;

-- Índice en la vista materializada
CREATE UNIQUE INDEX ON resumen_ventas_mensuales(mes, empresa);

-- Refrescar (puede ser automático con pg_cron)
REFRESH MATERIALIZED VIEW CONCURRENTLY resumen_ventas_mensuales;
-- CONCURRENTLY no bloquea lecturas durante el refresh
```
