{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# SELECT, WHERE y ORDER BY\n",
    "\n",
    "Pedir, filtrar y ordenar, con la primera diferencia gorda entre los cuatro motores.\n",
    "\n",
    "Cuaderno de práctica del capítulo 3 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/select-y-where/\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: \"aWQgICBmZWNoYSAgICAgICBtb250bwotLS0gIC0tLS0tLS0tLS0gIC0tLS0tLS0KMjUgICAyMDI1LTAxLTI1ICAxMzk5Ljk4CjQ0MCAgMjAyNS0xMS0wMiAgMTMyMi43Mwo3ODAgIDIwMjUtMDMtMjUgIDEzMTAuOTYKODc4ICAyMDI2LTA0LTE3ICAxMjA5Ljc5CjUzICAgMjAyNi0wNS0yMSAgMTE0NC41NQ==\",\n",
    "    2: \"T3BlcmF0aW9uYWxFcnJvcjogbmVhciAiNSI6IHN5bnRheCBlcnJvcg==\",\n",
    "    3: \"cGVkaWRvcwotLS0tLS0tCjk3\",\n",
    "    4: \"cGVkaWRvc19lbmVybwotLS0tLS0tLS0tLS0tCjUx\",\n",
    "    5: \"bm9tYnJlICAgICAgICAgICAgICAgICAgICAgIGNpdWRhZAotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSAgLS0tLS0tLS0KQXV0b3NlcnZpY2lvIE5vcnRlIDAwNyAgICAgIENoaWNsYXlvCkF1dG9zZXJ2aWNpbyBOb3J0ZSAwMDggICAgICBDaGljbGF5bwpBdXRvc2VydmljaW8gTm9ydGUgMDIyICAgICAgUGl1cmEKQXV0b3NlcnZpY2lvIE5vcnRlIDAyOCAgICAgIFRydWppbGxvCiAgQVVUT1NFUlZJQ0lPIE5PUlRFIDAzMiAgICBQaXVyYQ==\",\n",
    "    7: \"c2luX21hcmNhcgotLS0tLS0tLS0tCjA=\",\n",
    "    8: \"bm9tYnJlICAgICAgICAgICAgICAgICAgICAgICAgICBjaXVkYWQKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tICAtLS0tLS0tLQogIE1BUktFVCBDRU5UUkFMIDA2OSAgICAgICAgICAgIFRydWppbGxvCiAgTUFSS0VUIENFTlRSQUwgMDc4ICAgICAgICAgICAgQ2hpY2xheW8KICBNQVlPUklTVEEgUEVSVSAwNzQgICAgICAgICAgICBBcmVxdWlwYQogIE1JTklNQVJLRVQgQVVST1JBIDA2OCAgICAgICAgIEFyZXF1aXBhCiAgTUlOSU1BUktFVCBBVVJPUkEgMDcxICAgICAgICAgQ3VzY28KICBNSU5JTUFSS0VUIEVMIFNPTCAwMjYgICAgICAgICBBcmVxdWlwYQogIFJFU1RBVVJBTlRFIE1JUkFGTE9SRVMgMDUxICAgIEFyZXF1aXBhCiAgUkVTVEFVUkFOVEUgTUlSQUZMT1JFUyAwNzAgICAgQXJlcXVpcGEKQWxtYWNlbmVzIFZlZ2EgMDEwICAgICAgICAgICAgICBMaW1hCkFsbWFjZW5lcyBWZWdhIDAxNCAgICAgICAgICAgICAgUGl1cmE=\",\n",
    "    10: \"ZGVfbGFzX3RyZXMKLS0tLS0tLS0tLS0KNTQ=\",\n",
    "}, lenguaje=\"sql\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Hola! Aquí empieza lo bueno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Con lo de este capítulo ya puedes responder el 60% de lo que te van a pedir en\n",
    "un trabajo. En serio 💜\n",
    "\n",
    "Así que párate un segundo y piensa: **¿cuál es la pregunta que te piden todas las semanas en tu trabajo?** Esa misma, con lo de este capítulo, la vas a poder contestar tú sin pedírsela a nadie 💪"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El orden en que se escribe y el orden en que corre"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Esto va primero porque explica casi todos los errores del principio.\n",
    "\n",
    "Se escribe así:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "SELECT   columnas\n",
    "FROM     tabla\n",
    "WHERE    filtro\n",
    "ORDER BY orden\n",
    "LIMIT    cuántas\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Pero el motor lo ejecuta en otro orden: primero `FROM` (de dónde),\n",
    "después `WHERE` (qué filas), después `SELECT` (qué\n",
    "columnas), después `ORDER BY` y al final `LIMIT`.\n",
    "\n",
    "De ahí sale una consecuencia práctica: **en el WHERE no deberías usar\n",
    "un alias que creaste en el SELECT**, porque cuando el WHERE corre, el\n",
    "SELECT todavía no pasó.\n",
    "\n",
    "Y aquí tenemos el primer caso de este libro donde SQLite te miente por ser\n",
    "amable. Mira:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "q(\"\"\"\n",
    "SELECT id, monto * 1.18 AS con_igv\n",
    "FROM pedidos\n",
    "WHERE con_igv > 1000\n",
    "LIMIT 3;\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Funcionó.** Y esa es exactamente la trampa 😬\n",
    "\n",
    "SQLite es permisivo y te deja usar el alias. Los otros tres no, porque siguen\n",
    "el orden de ejecución al pie de la letra: en PostgreSQL, MySQL y SQL Server esa\n",
    "consulta te dice que la columna `con_igv` no existe.\n",
    "\n",
    "Así que una consulta que aquí corre perfecto revienta en cuanto la lleves al\n",
    "trabajo. Es justo el tipo de cosa por la que este libro marca los cuatro\n",
    "motores 💜\n",
    "\n",
    "Se arregla repitiendo el cálculo en el WHERE:\n",
    "\n",
    "La forma que viaja a los cuatro es repetir el cálculo en el WHERE:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "q(\"\"\"\n",
    "SELECT id, monto, monto * 1.18 AS con_igv\n",
    "FROM pedidos\n",
    "WHERE monto * 1.18 > 1000\n",
    "ORDER BY con_igv DESC\n",
    "LIMIT 3;\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y en el `ORDER BY` sí funciona el alias en los cuatro motores,\n",
    "porque el ORDER BY corre después del SELECT. Ahí no hay discusión 🙂"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Limitar filas: la diferencia que más muerde"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Traer solo las 5 primeras\n",
    "\n",
    "| PostgreSQL | `SELECT * FROM pedidos ORDER BY monto DESC LIMIT 5;` |\n",
    "|---|---|\n",
    "| MySQL | `SELECT * FROM pedidos ORDER BY monto DESC LIMIT 5;` |\n",
    "| SQL Server | `SELECT TOP 5 * FROM pedidos ORDER BY monto DESC;` |\n",
    "| SQLite | `SELECT * FROM pedidos ORDER BY monto DESC LIMIT 5;` |\n",
    "\n",
    "El TOP de SQL Server va pegado al SELECT, antes de las columnas. Si vienes de MySQL y escribes LIMIT, el error que sale no te ayuda nada."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Saltarse las 10 primeras y traer las 5 siguientes (paginar)\n",
    "\n",
    "| PostgreSQL | `SELECT * FROM pedidos ORDER BY id LIMIT 5 OFFSET 10;` |\n",
    "|---|---|\n",
    "| MySQL | `SELECT * FROM pedidos ORDER BY id LIMIT 10, 5;` |\n",
    "| SQL Server | `SELECT * FROM pedidos ORDER BY id OFFSET 10 ROWS FETCH NEXT 5 ROWS ONLY;` |\n",
    "| SQLite | `SELECT * FROM pedidos ORDER BY id LIMIT 5 OFFSET 10;` |\n",
    "\n",
    "MySQL acepta las dos formas, pero en su atajo el orden se invierte: primero cuántas saltas y después cuántas traes. Es un clásico para equivocarse."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y una regla que vale para los cuatro: **limitar sin ordenar no\n",
    "garantiza nada**. Sin `ORDER BY`, \"las 5 primeras\" son las 5\n",
    "que al motor le dé la gana devolver, y puede cambiar entre ejecuciones."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Filtrar con WHERE"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "q(\"\"\"\n",
    "SELECT id, canal, monto\n",
    "FROM pedidos\n",
    "WHERE monto > 1500\n",
    "ORDER BY monto DESC\n",
    "LIMIT 5;\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Los operadores son los que esperas y funcionan igual en los cuatro:\n",
    "`=`, `<>` o `!=` para distinto,\n",
    "`>`, `<`, `>=`, `<=`.\n",
    "\n",
    "### AND, OR y los paréntesis que cambian el resultado"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "q(\"\"\"\n",
    "SELECT COUNT(*) AS sin_parentesis\n",
    "FROM pedidos\n",
    "WHERE canal = 'Web' OR canal = 'Tienda' AND monto > 1000;\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "q(\"\"\"\n",
    "SELECT COUNT(*) AS con_parentesis\n",
    "FROM pedidos\n",
    "WHERE (canal = 'Web' OR canal = 'Tienda') AND monto > 1000;\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Números distintos, y la consulta no dio error en ninguno de los dos casos 😳\n",
    "\n",
    "El motivo: **`AND` se evalúa antes que `OR`**,\n",
    "como la multiplicación antes que la suma. Así que el primero significa \"todo lo\n",
    "de Web, más lo de Tienda que pase de 1000\".\n",
    "\n",
    "La regla que uso siempre: **si mezclas AND con OR, pon paréntesis\n",
    "aunque creas que no hacen falta**. Cuestan nada y evitan un reporte\n",
    "equivocado que nadie va a notar.\n",
    "\n",
    "### IN, BETWEEN y LIKE"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "q(\"\"\"\n",
    "SELECT COUNT(*) AS pedidos\n",
    "FROM pedidos\n",
    "WHERE canal IN ('Web', 'WhatsApp');\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "q(\"\"\"\n",
    "SELECT COUNT(*) AS del_primer_trimestre\n",
    "FROM pedidos\n",
    "WHERE fecha BETWEEN '2025-01-01' AND '2025-03-31';\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`BETWEEN` incluye los dos extremos, y ahí está su trampa: con\n",
    "fechas que llevan hora, `BETWEEN '2025-01-01' AND '2025-03-31'` se\n",
    "come todo el 31 de marzo salvo las horas posteriores a medianoche. Por eso en\n",
    "producción se escribe `>= inicio AND < día_siguiente`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "q(\"\"\"\n",
    "SELECT nombre FROM clientes\n",
    "WHERE nombre LIKE 'Bodega%'\n",
    "LIMIT 4;\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El `%` es \"lo que sea, incluido nada\", y el `_` es\n",
    "exactamente un carácter."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Buscar texto sin importar mayúsculas\n",
    "\n",
    "| PostgreSQL | `WHERE nombre ILIKE '%bodega%'` |\n",
    "|---|---|\n",
    "| MySQL | `WHERE nombre LIKE '%bodega%'   -- ya ignora mayúsculas por defecto` |\n",
    "| SQL Server | `WHERE nombre LIKE '%bodega%'   -- depende del collation de la base` |\n",
    "| SQLite | `WHERE LOWER(nombre) LIKE '%bodega%'` |\n",
    "\n",
    "Esta es de las peores, porque el mismo LIKE se comporta distinto en cada motor y ninguno te avisa. Si escribes LOWER() en los dos lados, funciona igual en los cuatro y no dependes de la configuración."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Los nulos, que no se comparan con igual"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "q(\"\"\"\n",
    "SELECT COUNT(*) AS con_igual\n",
    "FROM direcciones\n",
    "WHERE es_principal = NULL;\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Cero. Y no porque no haya nulos, sino porque **nada es igual a\n",
    "NULL**, ni siquiera otro NULL. NULL significa \"no se sabe\", y dos cosas\n",
    "que no se saben no se puede decir que sean iguales."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "q(\"\"\"\n",
    "SELECT COUNT(*) AS con_is_null\n",
    "FROM direcciones\n",
    "WHERE es_principal IS NULL;\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Se usa `IS NULL` y `IS NOT NULL`, y eso es igual en los\n",
    "cuatro motores. El capítulo 4 va entero sobre esto porque da para mucho más."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ordenar"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "q(\"\"\"\n",
    "SELECT nombre, ciudad\n",
    "FROM clientes\n",
    "ORDER BY ciudad ASC, nombre DESC\n",
    "LIMIT 5;\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`ASC` es ascendente y es el que se usa si no dices nada;\n",
    "`DESC` es al revés. Puedes ordenar por varias columnas, y se aplican\n",
    "en orden."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Dónde quedan los nulos al ordenar\n",
    "\n",
    "| PostgreSQL | `ORDER BY monto DESC NULLS LAST   -- controlable` |\n",
    "|---|---|\n",
    "| MySQL | `ORDER BY monto DESC   -- los nulos van al final` |\n",
    "| SQL Server | `ORDER BY monto DESC   -- los nulos van al final` |\n",
    "| SQLite | `ORDER BY monto DESC   -- los nulos van al final` |\n",
    "\n",
    "Ordenando de mayor a menor, PostgreSQL pone los nulos primero y los otros tres al final. Si tu top 10 empieza con filas vacías, es esto."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Un caso completo"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "q(\"\"\"\n",
    "SELECT c.nombre, c.ciudad, p.fecha, p.canal, p.monto\n",
    "FROM pedidos p\n",
    "JOIN clientes c ON c.id = p.id_cliente\n",
    "WHERE p.monto > 1200\n",
    "  AND p.canal IN ('Web', 'Marketplace')\n",
    "  AND c.ciudad = 'Lima'\n",
    "ORDER BY p.monto DESC\n",
    "LIMIT 5;\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ese `JOIN` lo vemos entero en el capítulo 7; aquí solo quería que\n",
    "vieras cómo se lee una consulta de trabajo real. Y el alias de tabla\n",
    "(`p`, `c`) es lo que la hace legible cuando hay dos o\n",
    "más 🌟"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El paréntesis que vale 215 pedidos"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Este es el error más caro de todo el capítulo, y no da error 😖\n",
    "\n",
    "Quieres los pedidos grandes de dos canales concretos. Lo escribes como se\n",
    "dice en voz alta:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "q(\"\"\"\n",
    "SELECT COUNT(*)\n",
    "FROM pedidos\n",
    "WHERE canal = 'Web' OR canal = 'Tienda' AND monto > 1000;\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "246 pedidos. Suena razonable, lo pones en el informe y te vas a comer 🥗\n",
    "\n",
    "Ahora con dos paréntesis:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "q(\"\"\"\n",
    "SELECT COUNT(*)\n",
    "FROM pedidos\n",
    "WHERE (canal = 'Web' OR canal = 'Tienda') AND monto > 1000;\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**31.** Ocho veces menos, con las mismas palabras y sin una sola\n",
    "queja del motor 😱\n",
    "\n",
    "Lo que pasa es que `AND` se evalúa antes que `OR`,\n",
    "igual que la multiplicación va antes que la suma. Así que la primera consulta\n",
    "dice de verdad:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Todos los de Web, más los de Tienda que pasen de mil."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y tú querías decir:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Los de Web o Tienda, siempre que pasen de mil."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Los 246 son los 234 de Web enteros más 12 de Tienda. El filtro del monto solo\n",
    "se aplicó a la mitad de la consulta 🫠\n",
    "\n",
    "La regla, y no tiene excepciones: **si en un WHERE hay un OR y un AND\n",
    "juntos, van paréntesis**. Aunque sepas la precedencia. No es por ti, es\n",
    "por quien lea la consulta dentro de seis meses, que puede ser tú."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Tres filtros que ahorran mucho paréntesis"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Justo por lo de arriba, cuanto menos `OR` escribas, mejor. Estos\n",
    "tres lo evitan casi siempre 🎯\n",
    "\n",
    "### IN, para una lista de valores"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "q(\"\"\"\n",
    "SELECT COUNT(*) AS solo_esos_dos\n",
    "FROM pedidos\n",
    "WHERE canal IN ('Web', 'Tienda');\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "440, que es exactamente lo que daba el `OR` de arriba sin el\n",
    "filtro del monto. Y ahora añadir el monto ya no tiene trampa:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "q(\"\"\"\n",
    "SELECT COUNT(*) AS los_grandes_de_esos_dos\n",
    "FROM pedidos\n",
    "WHERE canal IN ('Web', 'Tienda')\n",
    "  AND monto > 1000;\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "31, los mismos que con paréntesis 🙌\n",
    "\n",
    "Esa es la mejor razón para usar `IN`: no es que sea más corto, es\n",
    "que **no se puede escribir mal**. El paréntesis viene incluido.\n",
    "\n",
    "### BETWEEN, para un rango"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "q(\"\"\"\n",
    "SELECT COUNT(*) AS con_between\n",
    "FROM pedidos\n",
    "WHERE monto BETWEEN 500 AND 1000;\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "q(\"\"\"\n",
    "SELECT COUNT(*) AS a_mano\n",
    "FROM pedidos\n",
    "WHERE monto >= 500 AND monto <= 1000;\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El mismo número, y ahí está el detalle que hay que retener:\n",
    "**`BETWEEN` incluye los dos extremos** 📏\n",
    "\n",
    "Con dinero da igual, pero con fechas muerde. `BETWEEN '2026-01-01' AND\n",
    "'2026-01-31'` deja fuera todo lo que pasó el 31 después de medianoche si\n",
    "la columna guarda también la hora. Por eso con fechas y hora prefiero\n",
    "`>= el primero AND < el primero del mes siguiente`, que no tiene\n",
    "ese borde.\n",
    "\n",
    "### LIKE, para buscar dentro del texto"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "q(\"\"\"\n",
    "SELECT COUNT(*) AS empiezan_con_a\n",
    "FROM clientes\n",
    "WHERE nombre LIKE 'A%';\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El `%` significa \"lo que sea, incluso nada\". Va al final para\n",
    "\"empieza por\", al principio para \"termina en\", y a los dos lados para \"contiene\n",
    "en algún sitio\" 🔎\n",
    "\n",
    "Y el aviso que no se dice nunca: `LIKE '%algo%'`, con el comodín\n",
    "delante, **no puede usar un índice**. Con cien filas ni te enteras;\n",
    "con diez millones, esa consulta es la que tiene al equipo esperando. Lo\n",
    "desarrollo en el capítulo 14."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Los pedidos grandes de un canal\n",
    "\n",
    "Trae los 5 pedidos de WhatsApp con mayor monto."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 1\n",
    "-- tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. Escríbelo en SQL Server\n",
    "\n",
    "La misma consulta anterior, en T-SQL."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 2\n",
    "-- tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. Los paréntesis que faltaban\n",
    "\n",
    "Cuenta los pedidos de Web o Tienda que además pasen de\n",
    "S/800, escrito bien."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 3\n",
    "-- tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. Un rango de fechas bien escrito\n",
    "\n",
    "Cuenta los pedidos de enero de 2026, sin usar BETWEEN."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 4\n",
    "-- tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. Buscar por texto\n",
    "\n",
    "Encuentra los clientes cuyo nombre contenga \"Norte\"."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 5\n",
    "-- tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. El alias en el WHERE\n",
    "\n",
    "Usa un alias del SELECT dentro del WHERE. Va a funcionar,\n",
    "y esa es la trampa. Escribe también la versión que sí viaja a los otros tres."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7. Los nulos, bien preguntados\n",
    "\n",
    "Cuenta las direcciones que NO tienen marcado si son\n",
    "principales."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 7\n",
    "-- tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 8. Paginar\n",
    "\n",
    "Trae la segunda página de clientes, de 10 en 10, ordenados\n",
    "por nombre."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 8\n",
    "-- tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 9. De dónde salían los 246\n",
    "\n",
    "Demuestra que la consulta sin paréntesis contestaba otra\n",
    "pregunta. Cuenta las dos mitades por separado."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 10. Tres ciudades sin escribir tres OR\n",
    "\n",
    "Cuenta los clientes de Lima, Arequipa y Cusco."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 10\n",
    "-- tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 11. El BETWEEN de fechas, aquí y en la vida real\n",
    "\n",
    "Cuenta los pedidos del primer trimestre de 2026 de las dos\n",
    "formas y compara."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 12. Las tres posiciones del comodín\n",
    "\n",
    "Busca clientes por el nombre de tres maneras y mira qué\n",
    "cambia."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Los dos grupos que no suman el total"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y ahora una que no da error, no da un número raro, y aun así deja fuera un trozo de tus datos sin que te enteres 🕳️"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### La trampa\n",
    "\n",
    "Partes los pedidos en dos grupos para el reporte: los que pasan de 500 soles y los que no. Entre los dos tienen que estar todos.\n",
    "\n",
    "```\n",
    "SELECT COUNT(*) FROM pedidos WHERE monto > 500;\n",
    "-- 558\n",
    "\n",
    "SELECT COUNT(*) FROM pedidos WHERE monto <= 500;\n",
    "-- 315\n",
    "\n",
    "SELECT COUNT(*) FROM pedidos;\n",
    "-- 900\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",
    "Escribes SELECT monto * 1.18 AS con_igv FROM ventas WHERE con_igv > 1000 y te dice que con_igv no existe. Pero si lo acabas de crear arriba.\n",
    "\n",
    "a) El WHERE corre antes que el SELECT, así que el alias todavía no existe\n",
    "\n",
    "b) Falta poner el alias entre comillas\n",
    "\n",
    "c) Hay que repetir el cálculo en el WHERE porque SQL no guarda variables\n",
    "\n",
    "d) El alias solo vale si la columna es de la tabla"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 🔄 Se escribe SELECT primero y se ejecuta casi al final. Por eso el alias no\n",
    "sirve en el WHERE y sí en el ORDER BY.\n",
    "\n",
    "- ✂️ `LIMIT` en tres motores, `TOP` en SQL Server, y\n",
    "siempre con `ORDER BY`.\n",
    "\n",
    "- 🧮 Con AND y OR mezclados, paréntesis siempre.\n",
    "\n",
    "- 📅 Rangos de fecha con `>=` y `<`, no con\n",
    "BETWEEN.\n",
    "\n",
    "- 🕳️ Los nulos se preguntan con `IS NULL`, nunca con\n",
    "`=`.\n",
    "\n",
    "- 🔠 Para buscar texto sin importar mayúsculas, `LOWER()` en los dos\n",
    "lados y funciona igual en los cuatro.\n",
    "\n",
    "Y si de todo el capítulo te llevas una sola frase, que sea esta:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El orden en que escribes una consulta no es el orden en que se ejecuta."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y esto mismo, filtrar y ordenar, en Python se escribe distinto y significa\n",
    "lo mismo. Si quieres verlo desde el otro lado está en el\n",
    "[libro de Python](https://missyera.com/guias/python-desde-cero/) 🐍\n",
    "\n",
    "En el capítulo 4 vamos a los tipos de dato y a los nulos en serio, que es\n",
    "donde se producen los errores más caros y más silenciosos.\n",
    "\n",
    "Que tengas un hermoso día! 🌟"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 3 de **SQL desde cero**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/sql-desde-cero/select-y-where/\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
}
