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 VIEWSySHOW 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 esdefault. Usa el nombre real o quita el
filtro. Unirinformation_schemacon tablas reales en la misma query aún no
está soportado (da un error claro).
1.3 DESCRIBE — estructura, metadatos, historial
| Comando | Fuente | Devuelve |
|---|---|---|
DESCRIBE [TABLE] <ref> | PG datasets.schema | col_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_transactions | txn · 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 mismoWITH(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)
| Área | Funciones Spark traducidas |
|---|---|
| Tiempo actual | CURRENT_TIMESTAMP(), CURRENT_DATE(), NOW() |
| Regex | RLIKE (→ ~), 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) |
| Cardinalidad | APPROX_COUNT_DISTINCT(c[, err]) (→ exacto COUNT(DISTINCT c)) |
| Aritmética de fecha | DATE_ADD, DATE_SUB, ADD_MONTHS, MONTHS_BETWEEN, DATEDIFF(unit, a, b) |
| Epoch | UNIX_TIMESTAMP, FROM_UNIXTIME |
| Partes de fecha | DAYOFWEEK, WEEKOFYEAR, LAST_DAY, DATE_TRUNC, EXTRACT |
| Muestreo manual | HASH() (→ hashtext()) |
Regex — matices (paridad Spark):
REGEXP_REPLACEreemplaza todas las
ocurrencias (flagg, como Spark) y traduce backrefs$1→\1.
REGEXP_EXTRACTusa el grupo1por defecto;idx=0= match completo; sin
match devuelve''(no NULL). El patrón puede llevar comas (\d{1,3}) sin
romperse. JSON:GET_JSON_OBJECTopera sobre columnasObject/Array/Map
(cast defensivo a jsonb);explode/from_json/VARIANTse 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)
| Spark | Equivalente 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.default≠workspace.publicde los ejemplos Databricks. EnFROMda igual; en filtros de datos usa el nombre real. APPROX_COUNT_DISTINCTes exacto (no HLL) — mismo resultado, sin la velocidad aproximada.DAYOFWEEK: Postgres numera 0=domingo…6=sábado; Spark 1=domingo…7=sábado.information_schemano 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>