Published

Bloque 1 — Exploración y Análisis de Datos

Connect any source, model it as an ontology, transform it, and operationalize it, analytics, automation and machine learning, under one governed, self-hostable roof. --- Most teams stitch the...

Bloque 1 — Exploración y Análisis de Datos

📐 La matriz MEDIDA vive en carbon-sql-v0.md

Los estados de este documento (✅ hoy / ⚠️ parcial / 🔜) se escribieron a mano contra
el shim de Postgres, retirado el 2026-07-10. Ya no describen el motor que ejecuta.

El contrato vigente se genera midiendo cada sintaxis contra el motor real
(scripts/duckdb/carbon-sql-conformance.ts) y lleva su fecha y su versión de motor.
Éste queda como material histórico y de narrativa; para saber qué funciona, mira
el medido.

Los ejemplos usan el dominio de demo (flujos migratorios de estorninos). Recuerda la convención de coordenada: en FROM el nombre resuelve solo; en filtros de datos usa tu coordenada real (SHOW SCHEMAS).


1. Descubrimiento (Discovery)

1.1 SHOW — catálogos, esquemas, tablas

📐 La tabla de comandos y su salida se GENERAN:
carbon-sql-catalog-v1.md
(M3 · F5). Allí están las
formas exactas, el esquema de salida de cada comando, los límites y lo que NO
está en v1. Lo de aquí son los ejemplos, que es lo que este documento sabe hacer
bien; la tabla que había se escribió a mano y ya se había quedado corta (le faltaba
SHOW VIEWS y SHOW COLUMNS).

El patrón LIKE es un glob: * y % son «cualquier cosa» y ? es un carácter. El IN <cat>[.<schema>] es lenient: si el nombre no casa con uno real, se ignora (así un IN workspace.public estilo Databricks no vacía el resultado).

SHOW SCHEMAS IN workspace;
SHOW SCHEMAS EXTENDED IN workspace;                          -- + num_tables
SHOW TABLES IN workspace.public LIKE '*flujos*';
SHOW TABLES IN workspace.public LIKE '*ngs';                 -- terminan en 'ngs'
SHOW TABLE EXTENDED IN workspace.public LIKE 'monitoreo_*';  -- metadatos por tabla

1.2 information_schema.columns — buscar columnas en N tablas

Tabla virtual consultable (SELECT con WHERE/GROUP BY/JOIN/CTE). Una fila por columna de cada dataset tuyo. Columnas: table_catalog · table_schema · table_name · column_name · data_type · ordinal_position · is_nullable · column_default. Los data_type van en MAYÚSCULAS (acerca a los filtros estilo Databricks).

-- Buscar toda columna que contenga "fecha"
SELECT table_name, column_name, data_type
FROM workspace.information_schema.columns
WHERE column_name LIKE '%fecha%'
ORDER BY table_name;

-- Conteo de columnas por tabla
SELECT table_name, COUNT(*) AS num_columnas
FROM workspace.information_schema.columns
GROUP BY table_name ORDER BY num_columnas DESC;

-- Columnas comunes entre dos tablas (CTE + JOIN)
WITH a AS (SELECT column_name FROM workspace.information_schema.columns WHERE table_name = 'bronze_flujos'),
     b AS (SELECT column_name FROM workspace.information_schema.columns WHERE table_name = 'bronze_informes')
SELECT a.column_name FROM a INNER JOIN b ON a.column_name = b.column_name;

⚠️ El WHERE table_schema = 'public' de los ejemplos Databricks devuelve
vacío
si tu schema real es default. Usa el nombre real o quita el
filtro
. Unir information_schema con tablas reales en la misma query aún no
está soportado (da un error claro).

1.3 DESCRIBE — estructura, metadatos, historial

ComandoFuenteDevuelve
DESCRIBE [TABLE] <ref>PG datasets.schemacol_name · data_type · nullable
DESCRIBE TABLE EXTENDED <ref>PG datasets+ sección Detailed Table Information: Catalog, Schema, Table, Tier, Provider, Location, Row Count, Size, Owner, Service, Created
DESCRIBE HISTORY <ref> [LIMIT n]ledger dataset_transactionstxn · operation · status · row_count · size_bytes · committed_at
DESCRIBE TABLE workspace.public.flujos_migratorios_estorninos;
DESCRIBE TABLE EXTENDED workspace.public.flujos_migratorios_estorninos;
DESCRIBE HISTORY workspace.public.flujos_migratorios_estorninos LIMIT 10;

2. Análisis Exploratorio (EDA)

Todo SQL estándar sobre la tabla. Ejemplos representativos:

-- Vista general + conteo
SELECT * FROM bronze_ventas LIMIT 10;
SELECT COUNT(*) AS total FROM bronze_ventas;

-- Rango de fechas (DATEDIFF traducido a Postgres)
SELECT MIN(fecha) AS min, MAX(fecha) AS max,
       DATEDIFF(day, MIN(fecha), MAX(fecha)) AS dias_de_datos
FROM bronze_ventas;

-- Distribución por categoría
SELECT producto, COUNT(*) AS n, SUM(cantidad) AS total,
       ROUND(AVG(precio_unitario), 2) AS precio_medio
FROM bronze_ventas GROUP BY producto ORDER BY n DESC;

-- Estadísticas descriptivas
SELECT COUNT(*) AS tx, SUM(cantidad*precio_unitario) AS ingreso,
       ROUND(AVG(cantidad*precio_unitario),2) AS ticket_medio,
       ROUND(STDDEV(cantidad*precio_unitario),2) AS desv_std
FROM bronze_ventas;

-- Outliers (subquery sobre la misma tabla)
SELECT producto, cantidad, precio_unitario
FROM bronze_ventas
WHERE cantidad > (SELECT AVG(cantidad) + 2*STDDEV(cantidad) FROM bronze_ventas)
ORDER BY cantidad DESC;

3. Validación de Calidad de Datos

-- Completitud: % de nulos por columna
SELECT COUNT(*) AS total,
  ROUND(100.0*SUM(CASE WHEN especie_nombre_comun IS NULL THEN 1 ELSE 0 END)/COUNT(*),2) AS pct_especie_null,
  ROUND(100.0*SUM(CASE WHEN velocidad_promedio_kmh IS NULL THEN 1 ELSE 0 END)/COUNT(*),2) AS pct_vel_null
FROM bronze_flujos;

-- Unicidad: IDs duplicados
SELECT id, COUNT(*) AS veces FROM bronze_flujos
GROUP BY id HAVING COUNT(*) > 1;

-- Rangos válidos
SELECT id, velocidad_promedio_kmh, altitud_vuelo_metros FROM bronze_flujos
WHERE velocidad_promedio_kmh < 0 OR velocidad_promedio_kmh > 200
   OR altitud_vuelo_metros < 0 OR altitud_vuelo_metros > 15000;

-- Consistencia cross-table (NOT EXISTS, multi-tabla)
SELECT DISTINCT f.especie_nombre_comun FROM bronze_flujos f
WHERE NOT EXISTS (SELECT 1 FROM bronze_informes i WHERE i.especie = f.especie_nombre_comun);

-- Data profiling
SELECT COUNT(DISTINCT especie_nombre_comun) AS unicos,
       MIN(LENGTH(especie_nombre_comun)) AS len_min,
       MAX(LENGTH(especie_nombre_comun)) AS len_max
FROM bronze_flujos;

4. Consultas Ad-hoc y Avanzadas

Soportado y nativo en Postgres (funciona sobre la CTE del motor):

  • Joins: INNER / LEFT / RIGHT / FULL / CROSS, orphans, cardinalidad.
  • Agregación: GROUP BY, HAVING, STRING_AGG(DISTINCT …).
  • Window functions: ROW_NUMBER · RANK · DENSE_RANK · LAG · LEAD · FIRST_VALUE · LAST_VALUE, running totals, moving averages, OVER(PARTITION BY …).
  • Percentiles: PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY x).
  • CTEs de usuario: WITH a AS (…), b AS (…) SELECT … — el motor fusiona sus CTEs internas en el mismo WITH (ver engine.md).
-- CTE + window (porcentaje del total)
WITH stats AS (
  SELECT clima, COUNT(*) AS n
  FROM bronze_flujos GROUP BY clima
)
SELECT clima, n, ROUND(100.0*n/SUM(n) OVER(), 2) AS pct
FROM stats ORDER BY n DESC;

5. Funciones y Dialecto (Spark → Postgres)

5.1 Soportado (shim Nivel 1)

ÁreaFunciones Spark traducidas
Tiempo actualCURRENT_TIMESTAMP(), CURRENT_DATE(), NOW()
RegexRLIKE (→ ~), REGEXP_EXTRACT(str,pat[,idx]), REGEXP_REPLACE(str,pat,repl), CAST(x AS STRING)
Semi-estructurado (JSON)GET_JSON_OBJECT(col, '$.a.b') (→ (col)::jsonb #>> '{a,b}', retorno text)
CardinalidadAPPROX_COUNT_DISTINCT(c[, err]) (→ exacto COUNT(DISTINCT c))
Aritmética de fechaDATE_ADD, DATE_SUB, ADD_MONTHS, MONTHS_BETWEEN, DATEDIFF(unit, a, b)
EpochUNIX_TIMESTAMP, FROM_UNIXTIME
Partes de fechaDAYOFWEEK, WEEKOFYEAR, LAST_DAY, DATE_TRUNC, EXTRACT
Muestreo manualHASH() (→ hashtext())

Regex — matices (paridad Spark): REGEXP_REPLACE reemplaza todas las
ocurrencias (flag g, como Spark) y traduce backrefs $1\1.
REGEXP_EXTRACT usa el grupo 1 por defecto; idx=0 = match completo; sin
match devuelve '' (no NULL). El patrón puede llevar comas (\d{1,3}) sin
romperse. JSON: GET_JSON_OBJECT opera sobre columnas Object/Array/Map
(cast defensivo a jsonb); explode/from_json/VARIANT se difieren a Karma.

SELECT especie_nombre_comun, fecha_avistamiento,
       DATEDIFF(day, fecha_avistamiento, CURRENT_TIMESTAMP()) AS dias
FROM bronze_flujos
WHERE especie_cientifico RLIKE '^[SA]'
ORDER BY dias LIMIT 10;

5.2 Diferido a Karma — usa el equivalente Postgres (ya corre hoy)

SparkEquivalente que funciona ahora
PIVOT (… FOR col IN (…))SUM(CASE WHEN col = 'x' THEN … END)
DATE_FORMAT(d, 'yyyy-MM-dd')to_char(d, 'YYYY-MM-DD')
TABLESAMPLE (10 PERCENT)WHERE random() < 0.1
TABLESAMPLE (BUCKET …)WHERE MOD(ABS(hashtext(id::text)), 100) < 10
EXPLODE(arr)escribir a mano CROSS JOIN LATERAL jsonb_array_elements(arr::jsonb)
FROM_JSON(col, schema)GET_JSON_OBJECT(col, '$.campo') por campo (sin STRUCT tipado)

Estas son puro dialecto; cuando llegue Karma serán nativas (ver engine.md §4).


6. Tiering desde el query (medallón)

Optas una tabla al medallón renombrándola con prefijo bronze_/silver_/gold_. El motor renombra el dataset y estampa datasets.tier:

-- Un dataset normal (verde) → capa bronze
ALTER TABLE ventas RENAME TO bronze_ventas;

-- Varias a la vez (batch, admite comentarios)
ALTER TABLE flujos RENAME TO bronze_flujos;
ALTER TABLE informes RENAME TO bronze_informes;

El badge/color se actualizan solos (evento warehouse:changed). Sin prefijo reconocido → tier NULL (normal). Ver el paradigma completo en docs/architecture/medallion-tiers.md.


7. Matices y límites

  • Coordenada real main.defaultworkspace.public de los ejemplos Databricks. En FROM da igual; en filtros de datos usa el nombre real.
  • APPROX_COUNT_DISTINCT es exacto (no HLL) — mismo resultado, sin la velocidad aproximada.
  • DAYOFWEEK: Postgres numera 0=domingo…6=sábado; Spark 1=domingo…7=sábado.
  • information_schema no se une aún con tablas reales en la misma query.
  • El motor es regex-heurístico: SQL muy exótico puede topar con bordes; las formas comunes de EDA/ad-hoc están cubiertas y testeadas.

8. Referencia rápida

DESCUBRIMIENTO   SHOW SCHEMAS|TABLES [EXTENDED] [IN …] [LIKE '<glob>']
                 SHOW TABLE EXTENDED IN … LIKE '<glob>'
                 DESCRIBE [TABLE] [EXTENDED] <ref> · DESCRIBE HISTORY <ref> [LIMIT n]
                 SELECT … FROM <cat>.information_schema.columns
EDA / CALIDAD    SELECT · COUNT · MIN/MAX/AVG/SUM/STDDEV · GROUP BY · HAVING
                 CASE WHEN · subqueries · DISTINCT · LENGTH
AD-HOC           JOIN (INNER/LEFT/RIGHT/FULL/CROSS) · STRING_AGG · window OVER()
                 PERCENTILE_CONT WITHIN GROUP · WITH … (CTEs)
DIALECTO         CURRENT_TIMESTAMP()/DATE() · RLIKE · APPROX_COUNT_DISTINCT
                 REGEXP_EXTRACT(str,pat[,idx]) · REGEXP_REPLACE(str,pat,repl)
                 GET_JSON_OBJECT(col,'$.a.b')
                 DATE_ADD/SUB · ADD_MONTHS · MONTHS_BETWEEN · DATEDIFF(unit,a,b)
                 UNIX_TIMESTAMP · FROM_UNIXTIME · DAYOFWEEK · WEEKOFYEAR · LAST_DAY
TIERING          ALTER TABLE <t> RENAME TO bronze_|silver_|gold_<t>