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