Hola! Aquí es donde se atasca la gente
En el capítulo 1 escribiste un SELECT y salió. Pero en tu trabajo la base no está en un archivo al lado del cuaderno:
- Está en un servidor
- Con un usuario
- Una contraseña
- Un puerto 🔐
Y este paso, que ningún curso explica bien, es donde muchas personas se quedan. Vamos por partes.
Y una pregunta para que la lleves puesta: ¿sabes si los datos de tu trabajo están en una base o en un Excel? Si nunca lo preguntaste, hazlo esta semana. La respuesta cambia bastante lo que te conviene aprender primero 🔐
Qué es un cliente
Un cliente es el programa donde escribes tus consultas y ves los resultados. La base vive en el servidor; el cliente es tu ventana a ella.
| Motor | Cliente oficial | Nota |
|---|---|---|
| PostgreSQL | pgAdmin, o psql en terminal | psql es feo y es lo que usan los que saben |
| MySQL | MySQL Workbench | También sirve phpMyAdmin si es una web |
| SQL Server | SQL Server Management Studio (SSMS) | Solo Windows. Azure Data Studio es la alternativa multiplataforma |
| SQLite | DB Browser for SQLite | Gratis, ligero, abre el archivo y ya |
Y el que te recomiendo de verdad: DBeaver. Es gratis, funciona con los cuatro (y con veinte más), y así aprendes una sola interfaz para toda tu carrera. Yo lo tengo abierto todo el día 💜
La cadena de conexión
Para conectarte hacen falta cinco datos, siempre los mismos: servidor, puerto, base, usuario y contraseña. Lo que cambia es el puerto por defecto y cómo se escribe todo junto.
La cadena de conexión y el puerto de fábrica
| PostgreSQL | postgresql://usuario:clave@servidor:5432/mi_base |
| MySQL | mysql://usuario:clave@servidor:3306/mi_base |
| SQL Server | Server=servidor,1433;Database=mi_base;User Id=usuario;Password=clave; |
| SQLite | ruta/a/tienda.db (no hay servidor, usuario ni puerto) |
SQLite es el raro y por una buena razón: no tiene servidor. La base es el archivo, así que conectarte es abrirlo.
Tres avisos que te van a ahorrar una tarde:
- 🔑 La contraseña nunca va escrita en el código. Va en una variable de entorno o en un gestor de secretos. Si subes una a un repositorio, considérala quemada y cámbiala.
- 🌐 Si no conecta, casi siempre es red y no clave. Un firewall, una VPN que falta o el servidor que no acepta conexiones de fuera.
- 👤 Pide un usuario de solo lectura. Para analizar no necesitas poder borrar nada, y así no puedes romper nada por accidente.
Dónde viven los datos que vas a consultar
Un detalle que ahorra un malentendido incómodo el primer mes de trabajo: cuando pides acceso a "la base de datos" y te dan otra cosa, no es desconfianza.
La base donde se registran las ventas mientras ocurren se llama transaccional, y está afinada para escribir rapidísimo, no para que tú le hagas preguntas pesadas. Una consulta tuya con tres JOIN a las once de la mañana puede poner lenta la caja de la tienda 😬
Por eso casi todas las empresas tienen una copia aparte:
- 🏭 El data warehouse es esa copia ya ordenada en tablas, pensada justo para que la interrogues. El 90% del SQL que escribas en tu vida laboral va contra uno de estos.
- 🌊 El data lake es la copia cruda, tal como llegó, sin ordenar. Sirve para guardar todo por si acaso, y para trabajar ahí hace falta más herramienta que SQL.
- 🔁 Y el proceso que mueve los datos de un sitio a otro y los limpia por el camino se llama ETL: extraer, transformar y cargar. Suele correr de madrugada, y por eso el reporte de hoy a veces trae los datos de ayer.
Todo eso es trabajo de ingeniería de datos y no hace falta para este libro. Solo quiero que la palabra no te suene nueva el día que alguien la diga en una reunión 💛
Lo primero al entrar a una base que no conoces
Te dan acceso y te encuentras con doscientas tablas con nombres como
TB_MOV_CAB. Esto es lo que hago yo, en este orden 🔍
1. Qué tablas hay
SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name;
name ----------- clientes detalle direcciones pedidos productos
Listar las tablas
| PostgreSQL | SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'; |
| MySQL | SHOW TABLES; |
| SQL Server | SELECT name FROM sys.tables; |
| SQLite | SELECT name FROM sqlite_master WHERE type = 'table'; |
PostgreSQL, MySQL y SQL Server tienen information_schema.tables, que es lo estándar. Si escribes esa, la misma consulta te sirve en los tres.
2. Qué columnas tiene cada una
PRAGMA table_info(pedidos);
cid name type notnull dflt_value pk --- ---------- ------- ------- ---------- -- 0 id INTEGER 0 1 1 id_cliente INTEGER 0 0 2 fecha TEXT 0 0 3 canal TEXT 0 0 4 monto REAL 0 0
Ver las columnas de una tabla
| PostgreSQL | SELECT column_name, data_type FROM information_schema.columns WHERE table_name = 'pedidos'; |
| MySQL | DESCRIBE pedidos; |
| SQL Server | sp_help 'pedidos'; |
| SQLite | PRAGMA table_info(pedidos); |
El DESCRIBE de MySQL es el más cómodo y no existe en los otros. information_schema.columns vuelve a ser la forma que viaja.
Fíjate en la columna pk de la salida: dice que id es
la clave primaria, o sea lo que identifica a cada fila sin
repetirse. Eso lo vas a necesitar en el capítulo de los JOIN.
3. Cómo se relacionan
Los nombres ya te lo cuentan casi todo: pedidos.id_cliente
apunta a clientes.id. Esa es una clave foránea, el
hilo que une dos tablas.
PRAGMA foreign_key_list(detalle);
id seq table from to on_update on_delete match -- --- --------- ----------- -- --------- --------- ----- 0 0 productos id_producto id NO ACTION NO ACTION NONE 1 0 pedidos id_pedido id NO ACTION NO ACTION NONE
Ahí está el mapa de la base en dos líneas: detalle apunta a
productos y a pedidos. O sea que cada línea de detalle
es un producto dentro de un pedido 🌟
Ver las claves foráneas
| PostgreSQL | SELECT * FROM information_schema.table_constraints WHERE constraint_type = 'FOREIGN KEY'; |
| MySQL | SELECT * FROM information_schema.key_column_usage WHERE referenced_table_name IS NOT NULL; |
| SQL Server | SELECT * FROM sys.foreign_keys; |
| SQLite | PRAGMA foreign_key_list(detalle); |
Y un aviso sobre SQLite: por defecto NO obliga a que se cumplan. Están declaradas y no se revisan salvo que actives PRAGMA foreign_keys = ON. En los otros tres se cumplen siempre.
4. Cuánto pesa cada tabla y qué hay dentro
SELECT COUNT(*) AS pedidos, MIN(fecha) AS desde, MAX(fecha) AS hasta, COUNT(DISTINCT id_cliente) AS clientes, COUNT(DISTINCT canal) AS canales FROM pedidos;
pedidos desde hasta clientes canales ------- ---------- ---------- -------- ------- 900 2025-01-01 2026-06-24 119 4
Esa consulta la escribo siempre y en cualquier motor funciona igual. En una línea te dice el tamaño, el periodo que cubre y cuánta variedad hay. Si el periodo no es el que esperabas, ya te ahorraste el análisis entero 💜
Y mira el detalle que suelta gratis: hay 120 clientes en la tabla pero solo 119 hicieron algún pedido. O sea que hay uno que se dio de alta y nunca compró. Eso, en un negocio de verdad, es una pregunta 🔍
Y para ver qué valores toma una columna:
SELECT canal, COUNT(*) AS pedidos FROM pedidos GROUP BY canal ORDER BY pedidos DESC;
canal pedidos ----------- ------- Web 234 WhatsApp 231 Marketplace 229 Tienda 206
Mirar antes de contar
Antes de sacar un solo número, mira filas de verdad. Diez filas te dicen más que cualquier documentación.
SELECT * FROM pedidos LIMIT 5;
id id_cliente fecha canal monto -- ---------- ---------- ----------- ------ 1 69 2025-05-30 Web 892.06 2 95 2025-06-03 Marketplace 731.09 3 63 2025-09-23 Tienda 407.39 4 114 2025-02-18 Tienda 358.23 5 67 2025-07-09 Web 844.94
Ahí ya se ve que la fecha viene como texto, que el monto es decimal y que
id_cliente es un número que apunta a otra tabla.
Ese SELECT * está bien para mirar y mal para
trabajar: en una tabla de cuarenta columnas y millones de filas, traes
todo para nada. Para explorar, perfecto; en una consulta que va a quedar
guardada, nombra las columnas.
Las cuatro preguntas de los primeros cinco minutos
Te acaban de dar acceso a una base que no conoces y te piden un número. Antes de escribir la consulta que te pidieron, estas cuatro. Siempre en este orden 🗺️
1. ¿Qué tablas hay?
SELECT name, type FROM sqlite_master WHERE type IN ('table', 'view') ORDER BY type, name;
name type ----------- ----- clientes table detalle table direcciones table pedidos table productos table
Cinco tablas y ninguna vista. Ya sabes de qué tamaño es el problema: no es una base de doscientas tablas donde hay que buscar con lupa 🙂
Fíjate en que pregunté también por las vistas. Una vista se consulta igual que una tabla, así que si no preguntas por ellas te puedes pasar media hora reconstruyendo a mano un resumen que alguien ya dejó hecho.
2. ¿Qué columnas tiene la que me interesa?
PRAGMA table_info(pedidos);
cid name type notnull dflt_value pk --- ---------- ------- ------- ---------- -- 0 id INTEGER 0 1 1 id_cliente INTEGER 0 0 2 fecha TEXT 0 0 3 canal TEXT 0 0 4 monto REAL 0 0
Cinco columnas, y ahí está todo lo que necesitas saber para escribir la consulta 📋
- 🔑
pkvale 1 enid: esa es la clave primaria, la que no se repite. - 🔤
typete dice qué esperar.fechaes TEXT, o sea que en esta base las fechas son texto, y eso cambia cómo se filtra. - ❓
notnullen cero significa que esa columna admite huecos. Todas lo admiten aquí, así que hay que contar con nulos en cualquiera.
3. ¿Cómo se conectan entre sí?
PRAGMA foreign_key_list(detalle);
id seq table from to on_update on_delete match -- --- --------- ----------- -- --------- --------- ----- 0 0 productos id_producto id NO ACTION NO ACTION NONE 1 0 pedidos id_pedido id NO ACTION NO ACTION NONE
Esta es la que más tiempo ahorra y la que menos gente pregunta 🔗
Te está diciendo que detalle.id_producto apunta a
productos.id y que detalle.id_pedido apunta a
pedidos.id. O sea, te acaba de escribir los dos JOIN que ibas a
tener que adivinar.
Y ojo con lo que pasa cuando esto sale vacío, que es lo normal en bases viejas: significa que nadie declaró las relaciones, no que no existan. Ahí toca deducirlas por los nombres de las columnas y confirmarlas contando, que es justo lo que hacemos en el capítulo de los JOIN.
4. ¿Cuánto pesa cada cosa?
SELECT 'clientes' AS tabla, COUNT(*) AS filas FROM clientes UNION ALL SELECT 'pedidos', COUNT(*) FROM pedidos UNION ALL SELECT 'detalle', COUNT(*) FROM detalle UNION ALL SELECT 'productos', COUNT(*) FROM productos UNION ALL SELECT 'direcciones', COUNT(*) FROM direcciones ORDER BY filas DESC;
tabla filas ----------- ----- detalle 2682 pedidos 900 direcciones 164 clientes 120 productos 40
Y ahí se lee la forma del negocio sin que nadie te la explique 🧠
120 clientes, 900 pedidos, 2.682 líneas de detalle. O sea unos siete pedidos
por cliente y unas tres líneas por pedido. Eso ya te dice que
detalle es la tabla grande y que cualquier consulta que la toque va
a costar más que las demás.
También te dice qué esperar de un JOIN: si juntas pedidos con
detalle vas a pasar de 900 filas a 2.682, y eso no es un
error, es lo que tiene que pasar. Saberlo de antemano es lo que
distingue un JOIN bien hecho de uno que asusta.
Hasta dónde llegan los datos
La quinta pregunta, que va aparte porque casi nadie la hace y es la que más informes ha arruinado 📅
SELECT MIN(fecha) AS desde, MAX(fecha) AS hasta, COUNT(DISTINCT fecha) AS dias_con_pedidos FROM pedidos;
desde hasta dias_con_pedidos ---------- ---------- ---------------- 2025-01-01 2026-06-24 439
Del 1 de enero de 2025 al 24 de junio de 2026, con 439 días distintos 🗓️
Tres cosas que te acabas de ahorrar:
Que te pidan "lo de este mes" y no haya. Si hoy es agosto y los datos paran en junio, el informe iba a salir vacío y tú ibas a pensar que escribiste mal la consulta.
Comparar periodos que no existen. Enero de 2025 está entero, pero el último mes puede estar a medias. Comparar un junio incompleto contra un mayo completo es la forma más fácil de anunciar una caída que no ocurrió.
Los días sin pedidos. Entre esas dos fechas hay unos 540 días y solo 439 tienen pedidos. Los cien que faltan pueden ser domingos, o pueden ser un mes en que el sistema no registró nada. Eso se pregunta, no se deduce.
Ejercicios
1. El esquema de clientes
Mira qué columnas tiene la tabla clientes.
PRAGMA table_info(clientes);
cid name type notnull dflt_value pk --- ---------- ------- ------- ---------- -- 0 id INTEGER 0 1 1 nombre TEXT 1 0 2 ciudad TEXT 0 0 3 segmento TEXT 0 0 4 fecha_alta TEXT 0 0
2. La ficha de productos
Saca cuántos productos hay, cuántas categorías, y el precio mínimo y máximo.
SELECT COUNT(*) AS productos, COUNT(DISTINCT categoria) AS categorias, MIN(precio) AS mas_barato, MAX(precio) AS mas_caro FROM productos;
productos categorias mas_barato mas_caro --------- ---------- ---------- -------- 40 5 4.01 89.35
3. Los valores de una columna
Lista los segmentos de cliente con cuántos hay de cada uno.
SELECT segmento, COUNT(*) AS clientes FROM clientes GROUP BY segmento ORDER BY clientes DESC;
segmento clientes ---------- -------- Horeca 36 Bodega 35 Minimarket 25 Mayorista 24
Treinta de cada uno, o sea que la base está balanceada a propósito. En datos reales esto casi nunca sale tan redondo, y cuando sale conviene preguntarse por qué 👀
4. El periodo de la base
Averigua desde cuándo y hasta cuándo hay pedidos, y cuántos días son.
SELECT MIN(fecha) AS desde, MAX(fecha) AS hasta, CAST(julianday(MAX(fecha)) - julianday(MIN(fecha)) AS INTEGER) AS dias FROM pedidos;
desde hasta dias ---------- ---------- ---- 2025-01-01 2026-06-24 539
Ese julianday es de SQLite. Aquí es donde más se separan los
cuatro motores, y lo vemos entero en el capítulo 5.
5. Escríbelo para los cuatro
Sin ejecutar: escribe "lista las tablas de la base" en los cuatro motores, y di cuál de las formas te serviría en tres de ellos.
-- PostgreSQL, MySQL y SQL Server: la forma estándar SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'; -- en MySQL, el nombre de tu base -- Los atajos de cada uno SHOW TABLES; -- MySQL SELECT name FROM sys.tables; -- SQL Server SELECT name FROM sqlite_master WHERE type = 'table'; -- SQLite, el único sin information_schema
La de information_schema te sirve en tres. SQLite es el que no la
tiene, y tiene sentido: es un archivo, no un servidor con catálogo.
6. La tabla que no existe
Consulta una tabla inventada y lee el error.
SELECT * FROM ventas LIMIT 3;
OperationalError: no such table: ventas
En PostgreSQL sería relation "ventas" does not exist, en MySQL
Table 'mi_base.ventas' doesn't exist y en SQL Server
Invalid object name 'ventas'. Cuando te pase, casi siempre es que
estás en otra base o en otro esquema del que crees 🙃
7. La columna que sí es obligatoria
Mira las columnas de clientes y busca cuál no
admite huecos.
PRAGMA table_info(clientes);
cid name type notnull dflt_value pk --- ---------- ------- ------- ---------- -- 0 id INTEGER 0 1 1 nombre TEXT 1 0 2 ciudad TEXT 0 0 3 segmento TEXT 0 0 4 fecha_alta TEXT 0 0
Solo nombre tiene notnull en 1. Las otras cuatro
admiten nulos 🕳️
Eso es información de negocio disfrazada de detalle técnico: quien diseñó esta tabla decidió que un cliente puede entrar sin ciudad y sin segmento, pero no sin nombre. Así que contar clientes por ciudad va a dejar gente fuera, y hay que comprobarlo antes de presentar el número.
La columna dflt_value, que aquí sale vacía en todas, te diría
qué valor se pone solo cuando no mandas nada. Cuando tiene algo, es la otra
mitad de la historia.
8. Tres filas antes de escribir nada
Mira cómo son los datos de verdad, no cómo dice el esquema que son.
SELECT * FROM pedidos LIMIT 3;
id id_cliente fecha canal monto -- ---------- ---------- ----------- ------ 1 69 2025-05-30 Web 892.06 2 95 2025-06-03 Marketplace 731.09 3 63 2025-09-23 Tienda 407.39
El LIMIT 3 no es timidez, es la costumbre que te va a salvar en
producción 🛑
Y en tres filas ya sabes cosas que el esquema no dice: las fechas vienen en formato año-mes-día con ceros, que es el que ordena bien; el canal viene escrito con mayúscula inicial; y los montos tienen dos decimales.
Ese SELECT * es de los pocos sitios donde lo uso. Para explorar
está bien porque quieres verlo todo; en una consulta que va a quedarse escrita,
se nombran las columnas.
9. ¿Compraron todos?
Compara cuántos clientes hay con cuántos aparecen en algún pedido.
SELECT COUNT(*) AS pedidos, COUNT(DISTINCT id_cliente) AS clientes_que_compraron, (SELECT COUNT(*) FROM clientes) AS clientes_totales FROM pedidos;
pedidos clientes_que_compraron clientes_totales ------- ---------------------- ---------------- 900 119 120
119 de 120. Hay exactamente un cliente que nunca compró nada 🔍
Un solo cliente parece poca cosa, y por eso lo pongo: si hubieras hecho un JOIN normal entre las dos tablas, ese cliente habría desaparecido del informe sin que nadie lo notara. Uno entre ciento veinte no se ve.
Encontrar exactamente quién es y por qué se cae de los informes es lo que
hace el LEFT JOIN del capítulo 7. Aquí lo que importa
es saber que existe antes de escribir nada.
10. ¿Los cuatro canales llevan el mismo tiempo?
Antes de comparar canales, comprueba que sean comparables.
SELECT canal, COUNT(*) AS pedidos, MIN(fecha) AS primera, MAX(fecha) AS ultima FROM pedidos GROUP BY canal;
canal pedidos primera ultima ----------- ------- ---------- ---------- Marketplace 229 2025-01-02 2026-06-23 Tienda 206 2025-01-01 2026-06-23 Web 234 2025-01-03 2026-06-24 WhatsApp 231 2025-01-01 2026-06-22
Los cuatro arrancan la primera semana de enero de 2025 y llegan a la última semana de junio de 2026 ✅
O sea que sí son comparables, y esa frase es el resultado del ejercicio. Si uno hubiera arrancado en marzo de 2026, tendría cuatro meses de vida contra dieciocho, y decir "es el canal que menos vende" sería una barbaridad.
Esta comprobación cuesta una consulta y evita la comparación injusta más común que existe. La hago siempre antes de poner cuatro barras una al lado de otra 📊
La base que no existía y aun así te dejó entrar
Ya sabes conectarte y mirar lo que hay. Ahora mira lo que pasa cuando crees que estás conectada y no lo estás, que es de las que más rabia dan 👀
La trampa
Te conectas a la base, escribes tu primera consulta y te dice que la tabla no existe. Revisas el nombre de la tabla diez veces y está bien escrito.
sqlite3 tinda.db sqlite> .tables sqlite> SELECT * FROM clientes; -- Parse error: no such table: clientes
Qué está mal
Mira el nombre del archivo 👀 Es tienda.db y ahí dice tinda.db. Y aquí está lo que hace daño de verdad: SQLite no se queja de que el archivo no exista. Te crea una base nueva, vacía, con ese nombre, y te deja dentro.
El .tables no devuelve nada. No un error: nada. Y como el error que sí sale habla de la tabla, te pasas media hora buscando el problema en la tabla.
Lo primero que hago yo al conectarme, siempre, es .tables. Si sale vacío no estoy donde creo que estoy. Y en cuanto trabajo con una base de verdad, .databases me dice la ruta completa del archivo, que es la única forma de estar segura.
Comprueba que lo tienes
Te dan acceso a una base de producción que no conoces y te piden "las ventas del último trimestre". ¿Qué escribes primero?
- La consulta que lista las tablas y sus columnas
- SELECT * FROM ventas, para ver qué hay
- SELECT COUNT(*) FROM ventas, que es más ligero
- Le pregunto a alguien del equipo cómo se llaman las tablas
Lo que te llevas
- 🔌 Un cliente es tu ventana a la base. DBeaver te sirve para los cuatro.
- 🔑 La contraseña nunca en el código, y pide usuario de solo lectura.
- 🗺️ Al entrar a una base nueva: qué tablas hay, qué columnas, cómo se relacionan y cuánto pesan.
- 📖
information_schemaes la forma que viaja entre PostgreSQL, MySQL y SQL Server. - 👀 Mira diez filas de verdad antes de contar nada.
Y si de todo el capítulo te llevas una sola frase, que sea esta:
Si el .tables sale vacío, no estás donde crees que estás.
Y si vienes de Excel, el salto es más corto de lo que parece: una tabla es una hoja, una fila es una fila, y una consulta es un filtro que no se borra. La guía de Excel cuenta la otra mitad del camino 📊
En el capítulo 3 empieza lo bueno: filtrar. Y ahí aparece la primera diferencia gorda entre motores, la de limitar filas.
Que tengas lindo día! 🌸
Preguntas frecuentes
¿Qué es SSMS?
SQL Server Management Studio, el programa de Microsoft para trabajar con SQL Server. Es gratis y solo corre en Windows.
¿Con qué programa abro una base de datos?
DBeaver si quieres uno solo para todos los motores, DB Browser si es SQLite y quieres algo ligero, pgAdmin para PostgreSQL y SSMS para SQL Server.
¿Cómo veo qué tablas tiene una base de datos?
Cada motor tiene lo suyo, y el que funciona casi en todos es consultar information_schema.tables. En SQLite se mira sqlite_master.
Practica este capítulo 📓
Todo el código de arriba en un cuaderno que corre de principio a fin, y los ejercicios con una celda vacía para que los hagas tú. Se abre en Google Colab de un clic y no hay que instalar nada. Donde veas %%revisa, escribe tu respuesta y el cuaderno te dice si te salió.
¿Prefieres trabajar en tu máquina? Bájate el cuaderno de práctica o el de soluciones. Todos están también en github.com/soymissyera/MissYeraEjercicios.