Saltar a contenido

02 · Dialecto SQL

Cuaderno asociado

cuadernos/01_sql.ipynb. Volver al 00 - Índice de la demo.

DuckDB no es «SQL de juguete»: su dialecto es uno de los más potentes para el análisis de datos

Mapa de características

flowchart TB
    SQL["Dialecto DuckDB 2.0"]
    SQL --> Q["Consultas"]
    SQL --> T["Tipos"]
    SQL --> M["Modificación"]
    SQL --> PR["Productividad"]

    Q --> Q1["FROM-first"]
    Q --> Q2["QUALIFY"]
    Q --> Q3["ASOF JOIN"]
    Q --> Q4["PIVOT / UNPIVOT"]
    Q --> Q5["GROUP BY ALL"]
    Q --> Q6["NEAREST ... BY SIMILARITY"]

    T --> T1["STRUCT, LIST, MAP"]
    T --> T2["VARIANT"]
    T --> T3["DECIMAL e INTERVAL"]

    M --> M1["DML dentro de CTE"]
    M --> M2["CTE recursivas + USING KEY"]
    M --> M3["COPY / INSERT por lotes"]

    PR --> P1["COLUMNS(*) y * EXCLUDE"]
    PR --> P2["SUMMARIZE / DESCRIBE"]
    PR --> P3["Lambdas: lambda x, i: ..."]

1. FROM

El origen de datos puede ir delante; el SELECT es opcional si no hey proyección:

FROM ventas
WHERE importe > 100
ORDER BY importe DESC;

Sirve para explorar rápido y para encadenar pipelines. Cambia cómo se lee una consulta: primero el dato, luego la pregunta.

2. QUALIFY: filtrar sobre ventanas sin subconsultas

SELECT cliente_id, pedido_id, importe,
       row_number() OVER (PARTITION BY cliente_id ORDER BY importe DESC) AS rn
FROM pedidos
QUALIFY rn <= 3;          -- top-3 por cliente, sin CTE

3. AsOf JOIN: alineación de series temporales

AsOf Join

What Is an AsOf Join?

Time series data is not always perfectly aligned. Clocks may be slightly off, or there may be a delay between cause and effect. This can make connecting two sets of ordered data challenging. AsOf joins are a tool for solving this and other similar problems.

One of the problems that AsOf joins are used to solve is finding the value of a varying property at a specific point in time. This use case is so common that it is where the name came from:

Give me the value of the property as of this time.

More generally, however, AsOf joins embody some common temporal analytic semantics, which can be cumbersome and slow to implement in standard SQL.

El operador ASOF JOIN (As-Of Join) aporta la capacidad nativa de realizar coincidencias temporales inexactas o difusas (fuzzy temporal lookups), permitiendo obtener el valor o estado más reciente ("a la fecha/hora de...") sin necesidad de que las marcas de tiempo entre ambas tablas coincidan de forma exacta.

Ejemplo clásico: precios de mercado con timestamps irregulares y operaciones entre ellos.

SELECT o.ts, o.activo, p.precio
FROM operaciones o
ASOF JOIN precios p
  ON o.activo = p.activo AND o.ts >= p.ts;

4. PIVOT / UNPIVOT volteado en una línea

PIVOT ventas ON trimestre USING sum(importe) GROUP BY producto;

5. GROUP BY ALL

Deja que el motor agrupe por todo lo no agregado. Menos ruido, menos errores al añadir una columna:

SELECT pais, canal, sum(importe), count(*)
FROM ventas
GROUP BY ALL
ORDER BY ALL;

6. Lambda: Sintaxis nueva en 2.0

-- ❌ Desactivado por defecto en 2.0
SELECT list_transform([1, 2, 3], x -> x * 10);

-- ✅ Sintaxis nueva
SELECT list_transform([1, 2, 3], lambda x: x * 10);
SELECT list_transform([10, 20], lambda x, i: x + i);   -- con índice
SELECT list_reduce([1, 2, 3], lambda a, b: a + b);      -- reducción

Error que aparece al usar la sintaxis anterior

Binder Error: Deprecated lambda arrow (->) detected. Please transition to the new lambda syntax. Si tu código de la 1.x usaba x -> ..., hay que reescribirlo.

7. DML dentro de CTE (Nuevo en 2.0)

Insertar y consultar en la misma expresión de tabla:

WITH nuevos AS (
    INSERT INTO auditoria (evento, ts)
    VALUES ('carga_diaria', now())
    RETURNING *
)
SELECT * FROM nuevos;

Útil para pipelines que necesitan trazabilidad sin un segundo viaje a la base de datos.

8. Tipos que evitan el JSON "de pobres"

SELECT
    {'nombre': 'ana', 'edad': 31}          AS persona,   -- STRUCT
    [1, 2, 3]                              AS lista,     -- LIST
    MAP {'es': 'hola', 'en': 'hi'}         AS saludo,    -- MAP
    CAST(12.5 AS DECIMAL(10,2))            AS dinero;

Acceso por punto y por índice: persona.nombre, lista[1] (¡1-indexado!), saludo['es'].

9. Productividad en análisis exploratorio

SUMMARIZE SELECT * FROM ventas;   -- perfilado por columna: min, max, cuantiles, nulos
DESCRIBE SELECT * FROM ventas;    -- tipos inferidos
SELECT COLUMNS('importe.*') FROM ventas;   -- regex sobre nombres
SELECT * EXCLUDE (interno) FROM ventas;

SUMMARIZE es la primera consulta que deberías lanzar sobre cualquier tabla nueva.

10. NEAREST ... BY SIMILARITY

Búsqueda top-k por similitud como si fuera un JOIN — pensado para vectores y embeddings:

Alcance del taller

Montar embeddings reales exige la extensión vss y un modelo. Este taller únicamente presenta la funcionalidad.

SELECT d.titulo, n.distancia
FROM documentos d
NEAREST JOIN consultas q ON d.embedding BY SIMILARITY
ORDER BY distancia
LIMIT 5;


Ejercicios propuestos

  1. Top-2 de productos por facturación en cada categoría usando QUALIFY, sin subconsultas.
  2. Convertir SELECT categoria, sum(importe) FROM v GROUP BY categoria a la forma FROM-primero y añade ORDER BY ALL
  3. Reescribe estas lambdas de la 1.5 a la sintaxis 2.0:
    • list_filter([1,2,3,4], x -> x % 2 = 0)
    • list_transform(['a','bb'], (x, i) -> i || ':' || x)
  4. ¿Por qué SUMMARIZE es mejor primer paso que DESCRIBE?

Soluciones en cuadernos/99_soluciones.ipynb.