{
 "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 práctica 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",
    "Los ejercicios están al final y traen una celda vacía debajo de cada uno. Las\n",
    "respuestas viven en el cuaderno de soluciones, y merece la pena pelearse un\n",
    "rato antes de abrirlo 💛"
   ]
  },
  {
   "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": [
    "## Antes de empezar\n",
    "\n",
    "Esta celda baja el ayudante que corrige tus ejercicios. Después, en cada\n",
    "ejercicio que se pueda corregir solo, vas a ver `%%revisa` arriba de la celda:\n",
    "escribe tu respuesta debajo, ejecuta, y te digo si te salió 💛"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import urllib.request\n",
    "\n",
    "# El ayudante de los cuadernos. Trae la corrección de los ejercicios y, en los\n",
    "# capítulos de consola, la celda mágica que ejecuta los comandos. Se baja en\n",
    "# vez de venir pegado aquí para que siempre sea el último.\n",
    "urllib.request.urlretrieve(\n",
    "    \"https://missyera.com/static/cuadernos/revisa.py\", \"revisa.py\")\n",
    "import revisa\n",
    "revisa.carga({\n",
    "    1: \"Y2lkICBuYW1lICAgICAgICB0eXBlICAgICBub3RudWxsICBkZmx0X3ZhbHVlICBwawotLS0gIC0tLS0tLS0tLS0gIC0tLS0tLS0gIC0tLS0tLS0gIC0tLS0tLS0tLS0gIC0tCjAgICAgaWQgICAgICAgICAgSU5URUdFUiAgMCAgICAgICAgICAgICAgICAgICAgMQoxICAgIG5vbWJyZSAgICAgIFRFWFQgICAgIDEgICAgICAgICAgICAgICAgICAgIDAKMiAgICBjaXVkYWQgICAgICBURVhUICAgICAwICAgICAgICAgICAgICAgICAgICAwCjMgICAgc2VnbWVudG8gICAgVEVYVCAgICAgMCAgICAgICAgICAgICAgICAgICAgMAo0ICAgIGZlY2hhX2FsdGEgIFRFWFQgICAgIDAgICAgICAgICAgICAgICAgICAgIDA=\",\n",
    "    2: \"cHJvZHVjdG9zICBjYXRlZ29yaWFzICBtYXNfYmFyYXRvICBtYXNfY2FybwotLS0tLS0tLS0gIC0tLS0tLS0tLS0gIC0tLS0tLS0tLS0gIC0tLS0tLS0tCjQwICAgICAgICAgNSAgICAgICAgICAgNC4wMSAgICAgICAgODkuMzU=\",\n",
    "    3: \"c2VnbWVudG8gICAgY2xpZW50ZXMKLS0tLS0tLS0tLSAgLS0tLS0tLS0KSG9yZWNhICAgICAgMzYKQm9kZWdhICAgICAgMzUKTWluaW1hcmtldCAgMjUKTWF5b3Jpc3RhICAgMjQ=\",\n",
    "    4: \"ZGVzZGUgICAgICAgaGFzdGEgICAgICAgZGlhcwotLS0tLS0tLS0tICAtLS0tLS0tLS0tICAtLS0tCjIwMjUtMDEtMDEgIDIwMjYtMDYtMjQgIDUzOQ==\",\n",
    "    6: \"T3BlcmF0aW9uYWxFcnJvcjogbm8gc3VjaCB0YWJsZTogdmVudGFz\",\n",
    "    7: \"Y2lkICBuYW1lICAgICAgICB0eXBlICAgICBub3RudWxsICBkZmx0X3ZhbHVlICBwawotLS0gIC0tLS0tLS0tLS0gIC0tLS0tLS0gIC0tLS0tLS0gIC0tLS0tLS0tLS0gIC0tCjAgICAgaWQgICAgICAgICAgSU5URUdFUiAgMCAgICAgICAgICAgICAgICAgICAgMQoxICAgIG5vbWJyZSAgICAgIFRFWFQgICAgIDEgICAgICAgICAgICAgICAgICAgIDAKMiAgICBjaXVkYWQgICAgICBURVhUICAgICAwICAgICAgICAgICAgICAgICAgICAwCjMgICAgc2VnbWVudG8gICAgVEVYVCAgICAgMCAgICAgICAgICAgICAgICAgICAgMAo0ICAgIGZlY2hhX2FsdGEgIFRFWFQgICAgIDAgICAgICAgICAgICAgICAgICAgIDA=\",\n",
    "    8: \"aWQgIGlkX2NsaWVudGUgIGZlY2hhICAgICAgIGNhbmFsICAgICAgICBtb250bwotLSAgLS0tLS0tLS0tLSAgLS0tLS0tLS0tLSAgLS0tLS0tLS0tLS0gIC0tLS0tLQoxICAgNjkgICAgICAgICAgMjAyNS0wNS0zMCAgV2ViICAgICAgICAgIDg5Mi4wNgoyICAgOTUgICAgICAgICAgMjAyNS0wNi0wMyAgTWFya2V0cGxhY2UgIDczMS4wOQozICAgNjMgICAgICAgICAgMjAyNS0wOS0yMyAgVGllbmRhICAgICAgIDQwNy4zOQ==\",\n",
    "    9: \"cGVkaWRvcyAgY2xpZW50ZXNfcXVlX2NvbXByYXJvbiAgY2xpZW50ZXNfdG90YWxlcwotLS0tLS0tICAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tICAtLS0tLS0tLS0tLS0tLS0tCjkwMCAgICAgIDExOSAgICAgICAgICAgICAgICAgICAgIDEyMA==\",\n",
    "    10: \"Y2FuYWwgICAgICAgIHBlZGlkb3MgIHByaW1lcmEgICAgIHVsdGltYQotLS0tLS0tLS0tLSAgLS0tLS0tLSAgLS0tLS0tLS0tLSAgLS0tLS0tLS0tLQpNYXJrZXRwbGFjZSAgMjI5ICAgICAgMjAyNS0wMS0wMiAgMjAyNi0wNi0yMwpUaWVuZGEgICAgICAgMjA2ICAgICAgMjAyNS0wMS0wMSAgMjAyNi0wNi0yMwpXZWIgICAgICAgICAgMjM0ICAgICAgMjAyNS0wMS0wMyAgMjAyNi0wNi0yNApXaGF0c0FwcCAgICAgMjMxICAgICAgMjAyNS0wMS0wMSAgMjAyNi0wNi0yMg==\",\n",
    "}, lenguaje=\"sql\")"
   ]
  },
  {
   "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": [
    "%%revisa 1\n",
    "-- tu turno"
   ]
  },
  {
   "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": [
    "%%revisa 2\n",
    "-- tu turno"
   ]
  },
  {
   "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": [
    "%%revisa 3\n",
    "-- tu turno"
   ]
  },
  {
   "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": [
    "%%revisa 4\n",
    "-- tu turno"
   ]
  },
  {
   "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": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# tu turno"
   ]
  },
  {
   "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": [
    "%%revisa 6\n",
    "-- tu turno"
   ]
  },
  {
   "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": [
    "%%revisa 7\n",
    "-- tu turno"
   ]
  },
  {
   "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": [
    "%%revisa 8\n",
    "-- tu turno"
   ]
  },
  {
   "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": [
    "%%revisa 9\n",
    "-- tu turno"
   ]
  },
  {
   "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": [
    "%%revisa 10\n",
    "-- tu turno"
   ]
  },
  {
   "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?** La respuesta está en el cuaderno de soluciones. Míralo tú primero."
   ]
  },
  {
   "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"
   ]
  },
  {
   "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
}
