{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# La carga de las tres de la mañana\n",
    "\n",
    "Traer solo lo que llegó desde la última vez, sin duplicar nada y sin perder lo que llegó tarde. Con los dos errores que salen siempre.\n",
    "\n",
    "Cuaderno de práctica del capítulo 16 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/cargar-datos-cada-dia/\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: \"ZmlsYXMgIHZlbnRhCi0tLS0tICAtLS0tLS0tLS0KOTAxICAgIDUzMzEzNC4zNQ==\",\n",
    "    2: \"aWRfcGVkaWRvICBtb250bwotLS0tLS0tLS0gIC0tLS0tCjk5MDAxICAgICAgNTgwLjU=\",\n",
    "    3: \"ZW5fZWxfb3JpZ2VuICBlbl9lbF9hbG1hY2VuICBmYW50YXNtYXMKLS0tLS0tLS0tLS0tICAtLS0tLS0tLS0tLS0tICAtLS0tLS0tLS0KOTAwICAgICAgICAgICA5MDEgICAgICAgICAgICAx\",\n",
    "    4: \"Y29ycmlvX2VuICAgICAgICAgZmlsYXMgIGVzdGFkbwotLS0tLS0tLS0tLS0tLS0tICAtLS0tLSAgLS0tLS0tLS0tCjIwMjYtMDYtMjYgMDM6MDAgIDAgICAgICBzaW4gZGF0b3M=\",\n",
    "    5: \"bWFyY2FfZGVfYWd1YSAgbG9fcXVlX3RyYWVyaWFfbWFuYW5hCi0tLS0tLS0tLS0tLS0gIC0tLS0tLS0tLS0tLS0tLS0tLS0tLQoyMDk5LTAxLTAxICAgICAw\",\n",
    "}, lenguaje=\"sql\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "En el capítulo 15 armamos el almacén de una vez.\n",
    "Ahora la parte que se repite: **mañana llegan los pedidos de hoy**,\n",
    "y pasado los de mañana.\n",
    "\n",
    "Las dos formas de hacerlo son borrar todo y volver a cargar, o traer solo lo\n",
    "nuevo. La primera es más simple y aguanta bastante más de lo que la gente cree;\n",
    "la segunda es la que hace falta cuando la tabla ya no cabe en la ventana de la\n",
    "madrugada.\n",
    "\n",
    "Preparo la base con una columna que casi ninguna tabla de sistema trae de\n",
    "fábrica y que es el centro del capítulo:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "q(\"\"\"\n",
    "ALTER TABLE pedidos ADD COLUMN cargado_en TEXT;\n",
    "UPDATE pedidos SET cargado_en = fecha;\n",
    "\n",
    "SELECT count(*) AS pedidos, min(fecha) AS desde, max(fecha) AS hasta FROM pedidos;\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Novecientos pedidos, año y medio. De momento `cargado_en` es igual\n",
    "a `fecha`, o sea que hago como si cada pedido hubiera entrado el\n",
    "mismo día que se hizo. Dentro de un rato dejará de ser verdad, y ahí está el\n",
    "capítulo entero 🙂"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La marca de agua"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La idea es de una línea: *guarda hasta dónde cargaste, y la próxima vez\n",
    "trae lo que venga después*. Eso que guardas se llama marca de agua, o\n",
    "*watermark* si estás leyendo documentación.\n",
    "\n",
    "Lo bonito es que no hace falta guardarla en ningún sitio: se le pregunta a la\n",
    "tabla de destino."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "q(\"\"\"\n",
    "CREATE TABLE ventas_dw (\n",
    "    id_pedido  INTEGER PRIMARY KEY,\n",
    "    fecha      TEXT NOT NULL,\n",
    "    canal      TEXT,\n",
    "    monto      REAL,\n",
    "    cargado_en TEXT\n",
    ");\n",
    "\n",
    "INSERT INTO ventas_dw (id_pedido, fecha, canal, monto, cargado_en)\n",
    "SELECT id, fecha, canal, monto, cargado_en FROM pedidos\n",
    "WHERE cargado_en > (SELECT max(cargado_en) FROM ventas_dw);\n",
    "\n",
    "SELECT count(*) AS filas_cargadas FROM ventas_dw;\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Cero 😐\n",
    "\n",
    "Y no falló nada. La tabla estaba vacía, `max(cargado_en)` sobre una\n",
    "tabla vacía devuelve `NULL`, y cualquier comparación contra\n",
    "`NULL` da `NULL`, que no es verdadero. El\n",
    "`WHERE` no dejó pasar ni una fila.\n",
    "\n",
    "Es la lección de los nulos del capítulo 4 llegando a la\n",
    "peor hora posible: la primera carga de la historia, la que nadie mira porque\n",
    "\"acaba de empezar\". Se arregla con un `coalesce`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "q(\"\"\"\n",
    "INSERT INTO ventas_dw (id_pedido, fecha, canal, monto, cargado_en)\n",
    "SELECT id, fecha, canal, monto, cargado_en FROM pedidos\n",
    "WHERE cargado_en > coalesce((SELECT max(cargado_en) FROM ventas_dw), '0000-00-00')\n",
    "  AND cargado_en < '2026-06-20';\n",
    "\n",
    "SELECT count(*) AS filas, max(cargado_en) AS marca FROM ventas_dw;\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "890 filas y la marca en el 19 de junio. El `AND` del final está\n",
    "para simular que hoy es 20 de junio y quedan cuatro días por cargar."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Correrla otra vez, que es lo que va a pasar"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahora la carga de la noche siguiente. Y voy a escribirla con el error que se\n",
    "escribe siempre, que es usar `>=` en vez de `>` \"por\n",
    "si acaso\":"
   ]
  },
  {
   "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",
    "    INSERT INTO ventas_dw (id_pedido, fecha, canal, monto, cargado_en)\n",
    "    SELECT id, fecha, canal, monto, cargado_en FROM pedidos\n",
    "    WHERE cargado_en >= (SELECT max(cargado_en) FROM ventas_dw);\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: ventas_dw.id_pedido\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El `>=` vuelve a traer el día que ya estaba cargado, y la clave\n",
    "primaria lo caza. Y acá hay que decir algo importante: **que reviente es\n",
    "la buena noticia**.\n",
    "\n",
    "Sin esa clave primaria, esas filas habrían entrado dos veces, la carga habría\n",
    "terminado en verde, y la venta del 19 de junio saldría duplicada en el reporte\n",
    "del lunes sin que nadie lo note. La restricción del capítulo\n",
    "11 está haciendo exactamente su trabajo 💛"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Que se pueda correr dos veces: idempotencia"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La palabra suena a examen y la idea es simple: **correr la carga dos\n",
    "veces tiene que dejar la tabla igual que correrla una**.\n",
    "\n",
    "Hace falta porque las cargas se repiten todo el tiempo. Se cayó a mitad y la\n",
    "relanzas. Alguien la lanzó a mano sin saber que ya había corrido. El servidor se\n",
    "reinició y el planificador la disparó otra vez. Si repetir duplica, cualquiera\n",
    "de esas tres te ensucia el almacén.\n",
    "\n",
    "La forma es el upsert del capítulo 12:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "q(\"\"\"\n",
    "INSERT INTO ventas_dw (id_pedido, fecha, canal, monto, cargado_en)\n",
    "SELECT id, fecha, canal, monto, cargado_en FROM pedidos\n",
    "WHERE cargado_en >= (SELECT max(cargado_en) FROM ventas_dw)\n",
    "ON CONFLICT (id_pedido) DO UPDATE\n",
    "SET fecha = excluded.fecha, canal = excluded.canal, monto = excluded.monto,\n",
    "    cargado_en = excluded.cargado_en;\n",
    "\n",
    "SELECT count(*) AS filas, max(cargado_en) AS marca,\n",
    "       round(sum(monto), 2) AS venta FROM ventas_dw;\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Las 900. Y ahora la prueba, que es correr lo mismo otra vez:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "q(\"\"\"\n",
    "INSERT INTO ventas_dw (id_pedido, fecha, canal, monto, cargado_en)\n",
    "SELECT id, fecha, canal, monto, cargado_en FROM pedidos\n",
    "WHERE cargado_en >= (SELECT max(cargado_en) FROM ventas_dw)\n",
    "ON CONFLICT (id_pedido) DO UPDATE\n",
    "SET fecha = excluded.fecha, canal = excluded.canal, monto = excluded.monto,\n",
    "    cargado_en = excluded.cargado_en;\n",
    "\n",
    "SELECT count(*) AS filas, round(sum(monto), 2) AS venta FROM ventas_dw;\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Mismo número de filas y misma venta. Eso es idempotente, y es lo que te deja\n",
    "dormir cuando la carga falla a las tres de la mañana: relanzas y ya 🌙"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Los dos relojes"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Acá viene lo que más datos pierde en el mundo real, y es tan silencioso como\n",
    "el JOIN del capítulo anterior.\n",
    "\n",
    "Entra un pedido con fecha del 10 de junio, que se cargó hoy 25 porque el\n",
    "marketplace lo mandó tarde:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "q(\"\"\"\n",
    "INSERT INTO pedidos (id, id_cliente, fecha, canal, monto, cargado_en)\n",
    "VALUES (99001, 7, '2026-06-10', 'WhatsApp', 480.5, '2026-06-25');\n",
    "\n",
    "SELECT (SELECT count(*) FROM pedidos)   AS en_el_origen,\n",
    "       (SELECT count(*) FROM ventas_dw) AS en_el_almacen,\n",
    "       (SELECT max(fecha) FROM ventas_dw) AS marca_por_fecha,\n",
    "       (SELECT max(cargado_en) FROM ventas_dw) AS marca_por_carga;\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "901 en el origen, 900 en el almacén. Corre la carga usando la fecha del\n",
    "pedido, que es lo que hace casi todo el mundo:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "q(\"\"\"\n",
    "INSERT INTO ventas_dw (id_pedido, fecha, canal, monto, cargado_en)\n",
    "SELECT id, fecha, canal, monto, cargado_en FROM pedidos\n",
    "WHERE fecha >= (SELECT max(fecha) FROM ventas_dw)\n",
    "ON CONFLICT (id_pedido) DO UPDATE\n",
    "SET fecha = excluded.fecha, monto = excluded.monto, cargado_en = excluded.cargado_en;\n",
    "\n",
    "SELECT count(*) AS en_el_almacen FROM ventas_dw;\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Sigue en 900. La venta de 480,50 soles existe, entró bien, está en el origen,\n",
    "y tu almacén no la va a ver **nunca**, porque su fecha es más vieja\n",
    "que la marca de agua y ese pedido ya pasó de largo.\n",
    "\n",
    "La cura es cambiar de reloj:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "q(\"\"\"\n",
    "INSERT INTO ventas_dw (id_pedido, fecha, canal, monto, cargado_en)\n",
    "SELECT id, fecha, canal, monto, cargado_en FROM pedidos\n",
    "WHERE cargado_en >= (SELECT max(cargado_en) FROM ventas_dw)\n",
    "ON CONFLICT (id_pedido) DO UPDATE\n",
    "SET fecha = excluded.fecha, monto = excluded.monto, cargado_en = excluded.cargado_en;\n",
    "\n",
    "SELECT (SELECT count(*) FROM pedidos)   AS en_el_origen,\n",
    "       (SELECT count(*) FROM ventas_dw) AS en_el_almacen;\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Los dos relojes tienen nombre y conviene sabérselos porque salen en toda la\n",
    "documentación: la fecha del pedido es **hora del negocio**, y la de\n",
    "carga es **hora de ingesta**. Se reportan por la primera y se carga\n",
    "por la segunda 📏\n",
    "\n",
    "Y si el sistema de origen no guarda la hora de ingesta, esa es una petición\n",
    "de las que se piden el día uno, como en cualquier proyecto de datos."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La bitácora, que contesta \"¿corrió?\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Una tabla más, y es la que te salva de la peor pregunta de un lunes, que es\n",
    "\"¿esto está actualizado?\":"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "q(\"\"\"\n",
    "CREATE TABLE carga_log (\n",
    "    id         INTEGER PRIMARY KEY,\n",
    "    tabla      TEXT,\n",
    "    corrio_en  TEXT,\n",
    "    desde      TEXT,\n",
    "    filas      INTEGER,\n",
    "    estado     TEXT\n",
    ");\n",
    "\n",
    "INSERT INTO carga_log (tabla, corrio_en, desde, filas, estado) VALUES\n",
    "    ('ventas_dw', '2026-06-20 03:00', '2026-06-19', 10, 'ok'),\n",
    "    ('ventas_dw', '2026-06-25 03:00', '2026-06-24',  1, 'ok'),\n",
    "    ('ventas_dw', '2026-06-26 03:00', '2026-06-25',  0, 'sin datos');\n",
    "\n",
    "SELECT corrio_en, desde, filas, estado FROM carga_log ORDER BY corrio_en;\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Mira la última fila. Cero filas no es un error: puede ser un domingo sin\n",
    "ventas o puede ser que el origen dejó de mandar. Las dos se ven igual, y por eso\n",
    "se anota en vez de discutirse de memoria."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Enterarse de qué cambió en el origen, sin preguntar tabla por tabla\n",
    "\n",
    "| PostgreSQL | `replicación lógica, o una columna actualizado_en puesta por ti` |\n",
    "|---|---|\n",
    "| MySQL | `el binlog, que se suele leer con Debezium` |\n",
    "| SQL Server | `Change Data Capture, incluido en el motor` |\n",
    "| SQLite | `no tiene nada: se pone a mano con un TRIGGER` |\n",
    "\n",
    "Esto se llama captura de cambios, y lo cuento para que sepas que existe antes de que alguien te lo nombre en una reunión. Para casi todo lo que vas a hacer, una columna con la hora de ingesta y una marca de agua alcanzan de sobra, y se entienden leyéndolas."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "Tu carga de cada noche trae lo que llegó después de la última vez, y usa la fecha del pedido para saber cuál fue la última vez. ¿Dónde está el problema?\n",
    "\n",
    "a) En un pedido con fecha vieja que entra hoy: la carga nunca lo va a ver\n",
    "\n",
    "b) En que la fecha del pedido puede venir nula\n",
    "\n",
    "c) En que hay que ordenar por fecha antes de cargar\n",
    "\n",
    "d) En nada, es la forma correcta"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. La ventana, que es más simple que la marca\n",
    "\n",
    "En vez de traer lo nuevo, borra los últimos días y\n",
    "vuélvelos a cargar."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 1\n",
    "-- tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. El monto que cambió después\n",
    "\n",
    "Un pedido se corrige en el origen tres días más tarde.\n",
    "¿Llega la corrección?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 2\n",
    "-- tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. El borrado que el incremental no ve nunca\n",
    "\n",
    "El pedido se anula en el origen. Mira qué pasa."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 3\n",
    "-- tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. Que la bitácora te hable\n",
    "\n",
    "Escribe la consulta que quieres que alguien mire cada\n",
    "mañana."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 4\n",
    "-- tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. El dato sucio que congela la carga para siempre\n",
    "\n",
    "Entra un pedido con fecha del año 2099, de esos que salen\n",
    "de un formulario mal llenado."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 5\n",
    "-- tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. Completa o incremental, en tu caso\n",
    "\n",
    "Cuatro tablas. ¿Cuál cargarías entera cada noche?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Preguntas frecuentes"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "¿Qué es un watermark en un pipeline de datos?La marca de hasta dónde cargaste la última vez. La siguiente carga trae solo lo que venga después, y se le pregunta a la propia tabla de destino.\n",
    "\n",
    "¿Qué es la idempotencia?Que correr algo dos veces deje el mismo resultado que correrlo una. Es lo que te deja relanzar una carga que se cayó sin pensarlo dos veces.\n",
    "\n",
    "¿Qué es una carga incremental?Traer solo lo nuevo en vez de recargar la tabla entera. Hace falta cuando la carga completa ya no cabe en la ventana de la madrugada, y no antes."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 16 de **SQL desde cero**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/sql-desde-cero/cargar-datos-cada-dia/\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
}
