{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Cómo se diseña una base de datos\n",
    "\n",
    "Tablas, claves y relaciones, y las tres reglas de normalización contadas sin una sola palabra rara.\n",
    "\n",
    "Cuaderno de práctica del capítulo 10 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/diseno-de-la-base/\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: \"dGFibGEgICAgICAgICAgY29sdW1uYQotLS0tLS0tLS0tLS0tICAtLS0tLS0tCmNsaWVudGVzICAgICAgIGlkCmNsaWVudGVzX2JpZW4gIGlkCmRldGFsbGUgICAgICAgIGlkCmRpcmVjY2lvbmVzICAgIGlkCnBlZGlkb3MgICAgICAgIGlkCnBlZGlkb3NfcCAgICAgIGlkCnByb2R1Y3RvcyAgICAgIGlk\",\n",
    "    2: \"ZGlyZWNjaW9uZXMgIGNsaWVudGVzICBtZWRpYQotLS0tLS0tLS0tLSAgLS0tLS0tLS0gIC0tLS0tCjE2NCAgICAgICAgICAxMjAgICAgICAgMS4zNw==\",\n",
    "    3: \"bm9tYnJlICAgICAgIHBlZGlkb3MKLS0tLS0tLS0tLS0gIC0tLS0tLS0KUHJvZHVjdG8gMzggIDc5ClByb2R1Y3RvIDIyICA3NwpQcm9kdWN0byAxMyAgNzcKUHJvZHVjdG8gMjkgIDc1ClByb2R1Y3RvIDEwICA3NQ==\",\n",
    "    4: \"Y29sdW1uYXMKLS0tLS0tLS0KNg==\",\n",
    "    5: \"Y2l1ZGFkICAgIGNsaWVudGVzCi0tLS0tLS0tICAtLS0tLS0tLQpDaGljbGF5byAgMjUKUGl1cmEgICAgIDI0CkFyZXF1aXBhICAyMgpUcnVqaWxsbyAgMTcKQ3VzY28gICAgIDE3CkxpbWEgICAgICAxNQ==\",\n",
    "    6: \"cmVjbGFtb3MKLS0tLS0tLS0KMQ==\",\n",
    "    7: \"cHJlY2lvc19kaXN0aW50b3MgIHByb2R1Y3RvcwotLS0tLS0tLS0tLS0tLS0tLSAgLS0tLS0tLS0tCjQwICAgICAgICAgICAgICAgICA0MA==\",\n",
    "}, lenguaje=\"sql\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Llevas nueve capítulos consultando una base que alguien ya diseñó. Hoy toca\n",
    "el otro lado 🛠️\n",
    "\n",
    "Y es el capítulo que casi ningún curso da, porque enseñar `SELECT`\n",
    "se ve rápido y enseñar a diseñar se ve lento. Pero es al revés de lo que parece:\n",
    "**una base mal diseñada no se arregla con consultas**. Se arregla\n",
    "rehaciéndola, con los datos dentro, y eso sí es lento.\n",
    "\n",
    "Antes de arrancar, contéstate una: **¿alguna vez abriste un Excel del\n",
    "trabajo donde la misma información estaba en tres hojas distintas?** Si\n",
    "la respuesta es sí, ya sabes lo que se siente vivir sin diseño. Este capítulo va\n",
    "de por qué pasa eso y de cómo se evita 🗂️"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La regla que ordena casi todo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Una tabla por cada **cosa que existe de verdad**. Nada más.\n",
    "\n",
    "Un cliente existe. Un pedido existe. Un producto existe. Cada uno es una\n",
    "tabla. Y lo que no es una cosa sino una *relación entre cosas*, como \"este\n",
    "pedido lleva estos productos\", también termina siendo una tabla, pero de otro\n",
    "tipo. Ya la vamos a ver.\n",
    "\n",
    "Mira cómo está armada la base con la que llevas nueve capítulos:"
   ]
  },
  {
   "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": [
    "Cinco tablas y cinco cosas: los clientes, sus direcciones, los pedidos, los\n",
    "productos y las líneas de cada pedido. Nadie decidió eso al azar. Es el resultado\n",
    "de hacerse una pregunta por cada columna: **¿esto describe a la fila o es\n",
    "otra cosa que existe por su cuenta?** 🤔"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La clave primaria, o cómo se llama cada fila"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Cada tabla necesita una columna que **identifique la fila y no se\n",
    "repita nunca**. Eso es la clave primaria, y no es un adorno: es lo que\n",
    "hace que la base pueda decir \"esta fila y no otra\".\n",
    "\n",
    "Mira lo que pasa sin ella:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "q(\"\"\"\n",
    "CREATE TABLE clientes_mal (\n",
    "    id     INTEGER,\n",
    "    nombre TEXT,\n",
    "    ciudad TEXT\n",
    ");\n",
    "\n",
    "INSERT INTO clientes_mal VALUES (1, 'Bodega Aurora', 'Lima');\n",
    "INSERT INTO clientes_mal VALUES (1, 'Bodega Aurora', 'Lima');\n",
    "\n",
    "SELECT COUNT(*) AS filas FROM clientes_mal;\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Dos filas para un cliente 😐 La base no se quejó porque nadie le dijo que\n",
    "`id` tenía que ser único. Y esto no se queda ahí, mira lo que le hace\n",
    "a un total:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "q(\"\"\"\n",
    "CREATE TABLE pedidos_p (\n",
    "    id         INTEGER PRIMARY KEY,\n",
    "    id_cliente INTEGER,\n",
    "    monto      REAL\n",
    ");\n",
    "INSERT INTO pedidos_p VALUES (1, 1, 100.0), (2, 1, 250.0);\n",
    "\n",
    "SELECT COUNT(*) AS pedidos, SUM(monto) AS soles FROM pedidos_p;\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "q(\"\"\"\n",
    "SELECT COUNT(*) AS pedidos, SUM(p.monto) AS soles\n",
    "FROM pedidos_p p\n",
    "JOIN clientes_mal c ON c.id = p.id_cliente;\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Dos pedidos de 350 soles se convirtieron en cuatro de 700 💸 Es el mismo\n",
    "fan-out del capítulo 7, pero acá la causa no fue el JOIN: fue que la\n",
    "tabla permitía el duplicado desde el día uno.\n",
    "\n",
    "Con la clave primaria puesta, el segundo INSERT ni entra:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Esto revienta a propósito.** Se ejecuta dentro de un `try` para que puedas seguir con \"ejecutar todo\" y aun así ver la queja."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "try:\n",
    "    q(\"\"\"\n",
    "    CREATE TABLE clientes_bien (\n",
    "        id     INTEGER PRIMARY KEY,\n",
    "        nombre TEXT NOT NULL,\n",
    "        ciudad TEXT\n",
    "    );\n",
    "    INSERT INTO clientes_bien VALUES (1, 'Bodega Aurora', 'Lima');\n",
    "    INSERT INTO clientes_bien VALUES (1, 'Bodega Aurora', 'Lima');\n",
    "    \"\"\")\n",
    "except Exception as e:\n",
    "    print(f'{type(e).__name__}: {e}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y la queja que tiene que salir es esta:\n",
    "\n",
    "```\n",
    "IntegrityError: UNIQUE constraint failed: clientes_bien.id\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ese error es una buena noticia 🎉 La base te frenó en el momento en que se\n",
    "podía arreglar, en vez de dejarte descubrirlo tres meses después en un reporte\n",
    "que no cuadra.\n",
    "\n",
    "### Y cuál columna eliges\n",
    "\n",
    "La regla que uso: **una clave primaria no debe significar nada**.\n",
    "Un número que solo sirve para identificar la fila.\n",
    "\n",
    "La tentación es usar algo que ya tienes, como el DNI, el RUC o el correo. Y\n",
    "falla siempre por lo mismo: esas cosas cambian. El correo se cambia, el RUC se\n",
    "corrige porque estaba mal tecleado, y el día que cambia tienes que ir a\n",
    "actualizarlo en las siete tablas que lo estaban guardando. Un\n",
    "`id` que no significa nada no cambia nunca, porque no hay nada que\n",
    "pueda estar mal en él."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La clave foránea, o cómo se enganchan dos tablas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Una tabla apunta a otra **guardando su clave primaria**. Ya está,\n",
    "eso es todo el modelo relacional.\n",
    "\n",
    "La tabla `pedidos` no guarda el nombre del cliente. Guarda su\n",
    "`id`. Y esa columna se llama clave foránea porque es la clave de otra\n",
    "tabla, no la suya."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "q(\"\"\"\n",
    "SELECT COUNT(*) AS clientes FROM clientes;\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "q(\"\"\"\n",
    "SELECT COUNT(*) AS pedidos_sin_cliente\n",
    "FROM pedidos p\n",
    "LEFT JOIN clientes c ON c.id = p.id_cliente\n",
    "WHERE c.id IS NULL;\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Veinticuatro pedidos que apuntan a nadie 👻 En esta base pasa porque el\n",
    "`id_cliente` viene en NULL, y porque SQLite trae las claves foráneas\n",
    "apagadas de fábrica, que es una de las cosas que vamos a ver de cerca en el\n",
    "capítulo 11. En una base con la clave foránea encendida, esas 24 filas no habrían\n",
    "podido entrar.\n",
    "\n",
    "Y por eso vale la pena escribirla aunque tu motor no la aplique: el\n",
    "`REFERENCES` es documentación que además, en los otros tres motores,\n",
    "se cumple sola."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Si el motor aplica de verdad la clave foránea\n",
    "\n",
    "| PostgreSQL | `Siempre. No hay nada que encender` |\n",
    "|---|---|\n",
    "| MySQL | `Siempre con InnoDB, que es el motor de tablas por defecto` |\n",
    "| SQL Server | `Siempre. No hay nada que encender` |\n",
    "| SQLite | `Solo si haces PRAGMA foreign_keys = ON, y en cada conexión` |\n",
    "\n",
    "Es la diferencia más cara de SQLite y la que hace que se pueda aprender con una red que no está puesta. Escribe siempre el REFERENCES: en tres motores te protege, y en el cuarto al menos documenta lo que quisiste decir."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Uno a muchos, que es casi todo lo que vas a ver"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Un cliente tiene muchos pedidos. Un pedido tiene un solo cliente. Eso es una\n",
    "relación de **uno a muchos**, y se resuelve poniendo la clave del\n",
    "lado \"uno\" en la tabla del lado \"muchos\".\n",
    "\n",
    "La pregunta que decide dónde va la clave es siempre la misma: **¿de\n",
    "qué lado puede haber varios?** Ahí va la columna."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "q(\"\"\"\n",
    "SELECT c.nombre, COUNT(d.id) AS direcciones\n",
    "FROM clientes c\n",
    "JOIN direcciones d ON d.id_cliente = c.id\n",
    "GROUP BY c.id\n",
    "ORDER BY direcciones DESC, c.nombre\n",
    "LIMIT 4;\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Hay clientes con tres direcciones. Por eso la dirección es su propia tabla y\n",
    "no tres columnas en `clientes` 📍\n",
    "\n",
    "Y fíjate en el detalle de las columnas repetidas, porque es el error que más\n",
    "he visto: `direccion1`, `direccion2`,\n",
    "`direccion3`. Funciona hasta que aparece el cliente con cuatro. Y\n",
    "entonces hay que cambiar la tabla, cambiar todas las consultas y cambiar el\n",
    "formulario. **Si necesitas numerar columnas, lo que necesitas es otra\n",
    "tabla.**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Muchos a muchos, y la tabla que aparece en medio"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Un pedido lleva varios productos. Y un producto está en varios pedidos. Los\n",
    "dos lados son \"muchos\", así que la clave no cabe en ninguna de las dos\n",
    "tablas 🤷\n",
    "\n",
    "Se resuelve con una tercera tabla que solo existe para guardar la relación.\n",
    "En esta base se llama `detalle`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "q(\"\"\"\n",
    "SELECT COUNT(*) AS lineas,\n",
    "       COUNT(DISTINCT id_pedido)   AS pedidos,\n",
    "       COUNT(DISTINCT id_producto) AS productos\n",
    "FROM detalle;\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "2.682 líneas para conectar 900 pedidos con 40 productos. Cada línea dice\n",
    "\"este pedido lleva este producto\", y de paso guarda lo que solo tiene sentido en\n",
    "el cruce: cuántas unidades y a qué precio se vendió ese día 💡\n",
    "\n",
    "Esa es la señal de que la tabla puente está bien pensada: **guarda\n",
    "cosas que no pertenecen a ninguno de los dos lados**. La cantidad no es\n",
    "del producto ni del pedido: es de la línea."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Las tres reglas de normalización, sin jerga"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Normalizar suena a examen y son tres ideas de sentido común. Te las cuento\n",
    "como las uso yo 🌸\n",
    "\n",
    "- **Una celda, un dato.** Nada de meter tres cosas separadas por\n",
    "comas en la misma columna. (Primera forma normal.)\n",
    "\n",
    "- **Cada columna describe a la fila entera.** Si una columna solo\n",
    "depende de una parte de la clave, es de otra tabla. (Segunda.)\n",
    "\n",
    "- **Ninguna columna describe a otra columna.** Si guardas la\n",
    "ciudad y también el departamento, el departamento describe a la ciudad y no al\n",
    "cliente. (Tercera.)\n",
    "\n",
    "La primera es la que más se rompe y la que más caro sale, así que le vamos a\n",
    "dedicar la trampa del capítulo entera."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Tipos de base de datos, y cuándo no quieres una relacional"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Todo este libro va de bases relacionales, que son las que usa el 100% de los\n",
    "trabajos donde vas a escribir SQL. Pero hay otras, y conviene saber que existen\n",
    "para no pedir una por moda 🧭"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| Tipo | Cómo guarda | Cuándo tiene sentido |\n",
    "|---|---|---|\n",
    "| **Relacional** PostgreSQL, MySQL, SQL Server, SQLite | Tablas con filas y columnas, y relaciones entre ellas | Casi siempre. Cuando los datos tienen forma y te importa que cuadren |\n",
    "| **Documental** MongoDB | Documentos tipo JSON, cada uno con las claves que quiera | Cuando cada registro trae campos distintos y no sabes cuáles de antemano |\n",
    "| **Clave y valor** Redis | Una llave y su valor, nada más | Cachés y sesiones. Buscas por la llave y ya |\n",
    "| **Columnar** BigQuery, Redshift | Por columnas en vez de por filas | Analítica sobre muchísimas filas, cuando lees pocas columnas de golpe |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y te doy mi opinión, que en esto tengo una clara: **si dudas, es\n",
    "relacional**. La mayoría de los proyectos que eligen otra cosa lo hacen\n",
    "porque suena moderno, y terminan reinventando a mano las relaciones que la base\n",
    "relacional les daba gratis 💜"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Cuándo se desnormaliza a propósito"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Todo lo anterior tiene una excepción y no es contradicción, es un cambio de\n",
    "objetivo.\n",
    "\n",
    "Normalizas para **escribir**: que un dato viva en un solo sitio y\n",
    "no puedas contradecirte. Desnormalizas para **leer**: repites un\n",
    "dato a propósito para no tener que juntar seis tablas cada vez.\n",
    "\n",
    "Por eso un almacén de datos para reportes casi siempre está desnormalizado, y\n",
    "la base donde se factura casi nunca. Son dos bases con dos trabajos distintos, y\n",
    "esa diferencia la contamos en el capítulo 2.\n",
    "\n",
    "La regla corta: **desnormalizar es una decisión, nunca un\n",
    "descuido**. Si repites un dato, que sea porque lo decidiste y sepas quién\n",
    "lo mantiene al día 🎯"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Los ejercicios son la mitad del libro. Intenta antes de abrir."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Encuentra la clave primaria de cada tabla\n",
    "\n",
    "Sin abrir el esquema a mano, pídeselo a la base."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 1\n",
    "-- tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. Cuenta las relaciones uno a muchos\n",
    "\n",
    "¿Cuántas direcciones tiene de media un cliente, y cuántos\n",
    "tienen más de una?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 2\n",
    "-- tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. La tabla puente, mirada de cerca\n",
    "\n",
    "Saca los cinco productos que aparecen en más pedidos."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 3\n",
    "-- tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. Diseña la tabla que falta\n",
    "\n",
    "La tienda quiere registrar qué vendedor atendió cada\n",
    "pedido. Un vendedor atiende muchos pedidos, un pedido lo atiende un vendedor.\n",
    "¿Dónde va la clave?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 4\n",
    "-- tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. La tercera forma normal, en una tabla de verdad\n",
    "\n",
    "Imagina que a `clientes` le añadimos\n",
    "`departamento`. ¿Rompe alguna regla?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 5\n",
    "-- tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. El error que vas a cometer al crear tu primera tabla\n",
    "\n",
    "Crea una tabla poniendo la clave foránea a una tabla que\n",
    "todavía no existe."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 6\n",
    "-- tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7. Cuenta cuántas veces se repite un dato que no debería\n",
    "\n",
    "En la tabla de detalle está `precio_unit`. ¿Es\n",
    "un descuido o es a propósito?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 7\n",
    "-- tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La columna que guarda tres cosas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Antes de cerrar, la trampa del capítulo. Es la primera forma normal rota, que\n",
    "es la regla que más se rompe y la que más caro sale 🍬"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### La trampa\n",
    "\n",
    "El producto puede estar en varias categorías, así que las guardas todas en la misma columna separadas por comas. Es lo natural y ocupa una columna.\n",
    "\n",
    "```\n",
    "-- categorias: 'Bebidas' / 'Bebidas,Sin azucar'\n",
    "-- 'Snacks,Bebidas calientes' / 'Bebidas,Snacks'\n",
    "\n",
    "SELECT COUNT(*) FROM prod WHERE categorias = 'Bebidas';\n",
    "-- 1\n",
    "\n",
    "SELECT COUNT(*) FROM prod WHERE categorias LIKE '%Bebidas%';\n",
    "-- 4\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",
    "Tu tabla de clientes guarda ciudad y también departamento. Chiclayo aparece en 25 filas, y en las 25 dice Lambayeque. ¿Qué está mal?\n",
    "\n",
    "a) El departamento describe a la ciudad, no al cliente, así que va en otra tabla\n",
    "\n",
    "b) Nada, es más rápido tenerlo ahí y evitas un JOIN\n",
    "\n",
    "c) Falta ponerle una clave primaria a la columna departamento\n",
    "\n",
    "d) Habría que guardar el departamento en una sola fila y las otras 24 vacías"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 🗂️ Una tabla por cada cosa que existe de verdad.\n",
    "\n",
    "- 🔑 Clave primaria en todas, y que no signifique nada. El DNI y el correo\n",
    "cambian, un `id` no.\n",
    "\n",
    "- 🔗 Uno a muchos se resuelve poniendo la clave en el lado donde hay varios.\n",
    "La pregunta es siempre \"¿de qué lado puede haber varios?\".\n",
    "\n",
    "- 🌉 Muchos a muchos necesita una tabla en medio, y esa tabla guarda lo que no\n",
    "es de ninguno de los dos lados.\n",
    "\n",
    "- 🔢 Si necesitas numerar columnas (`direccion1`,\n",
    "`direccion2`), lo que necesitas es otra tabla.\n",
    "\n",
    "- 🧼 Las tres reglas: una celda un dato, cada columna describe a la fila\n",
    "entera, ninguna columna describe a otra columna.\n",
    "\n",
    "- 🧭 Si dudas del tipo de base, es relacional.\n",
    "\n",
    "- 🎯 Desnormalizar es una decisión, nunca un descuido.\n",
    "\n",
    "Y si de todo el capítulo te llevas una sola frase, que sea esta:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Una base mal diseñada no se\n",
    "arregla con consultas. Se arregla rehaciéndola, y con los datos dentro."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y esto de decidir qué columna existe, de qué tipo y qué se hace con lo que\n",
    "falta es media conversación con el cliente antes de tocar nada. Tiene nombre y\n",
    "está contado en el [libro de machine\n",
    "learning](https://missyera.com/guias/machine-learning-desde-cero/): el contrato de datos 🤝\n",
    "\n",
    "En el capítulo 11 pasamos del papel al teclado:\n",
    "`CREATE TABLE` de verdad, con sus tipos, sus restricciones y los\n",
    "cuatro autoincrementos que cada motor llama distinto.\n",
    "\n",
    "Que tengas lindo día! 🌸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Preguntas frecuentes"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "¿Qué es una base de datos relacional?Una base donde los datos viven en tablas que se relacionan por claves, en vez de estar todo junto en una sola. Es el diseño de casi todo sistema de empresa.\n",
    "\n",
    "¿Qué es el modelo relacional?La idea de guardar cada cosa una sola vez y unir las tablas cuando haga falta. Ahorra errores al escribir y cuesta un JOIN al leer.\n",
    "\n",
    "¿Qué es la normalización de una base de datos?Partir las tablas para que ningún dato esté repetido. Se llega hasta la tercera forma normal y con eso alcanza para casi todo.\n",
    "\n",
    "¿Cuál es la diferencia entre clave primaria y clave foránea?La primaria identifica una fila dentro de su tabla y no se repite. La foránea es una columna que apunta a la primaria de otra tabla."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 10 de **SQL desde cero**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/sql-desde-cero/diseno-de-la-base/\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
}
