Aprende SQL · lección gratuita

Lección 46 · NoSQL: cuando el modelo relacional no es la respuesta

Resumen

NoSQL significa "Not Only SQL" — no es "anti-SQL", es una familia de motores que abandonan o relajan el modelo de tablas relacionadas para casos donde ese modelo no encaja bien. No compiten con lo que aprendiste: coexisten. La recomendación por defecto sigue siendo empezar por una base relacional (todo este curso) y sumar NoSQL solo cuando un problema concreto lo justifique — típicamente escritura masiva, escalado horizontal extremo, o datos que no tienen una forma tabular natural.

Esta lección es de lectura: no tiene ejercicios evaluados porque MongoDB y Redis no son SQL — no hay forma de ejecutarlos ni corregirlos con el motor SQLite que usó todo el curso. El objetivo es que reconozcas el paisaje, no que lo practiques aquí.

La diferencia central: normalizar vs. embeber

Todo el curso trabajaste con datos normalizados: clientes y pedidos son tablas separadas, conectadas por una clave foránea, y las juntas con JOIN cuando las necesitas juntas. Una base de datos de documentos como MongoDB suele hacer lo contrario: embebe los datos relacionados directamente dentro del mismo registro.

<svg viewBox="0 0 760 280" width="100%" height="auto" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Comparación entre una base relacional con dos tablas unidas por JOIN, y un documento de MongoDB con los datos embebidos"> <defs> <marker id="arrow-nosql-1" markerWidth="8" markerHeight="8" refX="6" refY="4" orient="auto"> <path d="M0,0 L8,4 L0,8 Z" fill="#8b5cf6"/> </marker> </defs> <rect x="20" y="20" width="340" height="240" rx="12" fill="#111827" stroke="#374151"/> <text x="190" y="45" text-anchor="middle" fill="#a78bfa" font-weight="bold" font-size="15">SQL relacional</text> <rect x="50" y="65" width="280" height="55" rx="8" fill="#1f2937" stroke="#4b5563"/> <text x="190" y="86" text-anchor="middle" fill="#ffffff" font-weight="bold" font-size="13">clientes</text> <text x="190" y="106" text-anchor="middle" fill="#9ca3af" font-size="11.5" font-family="monospace">id · empresa · pais</text> <line x1="190" y1="120" x2="190" y2="150" stroke="#8b5cf6" stroke-width="2" marker-end="url(#arrow-nosql-1)"/> <text x="200" y="138" fill="#8b5cf6" font-size="10" font-style="italic">JOIN ON</text> <text x="200" y="150" fill="#8b5cf6" font-size="10" font-style="italic">cliente_id = id</text> <rect x="50" y="155" width="280" height="55" rx="8" fill="#1f2937" stroke="#4b5563"/> <text x="190" y="176" text-anchor="middle" fill="#ffffff" font-weight="bold" font-size="13">pedidos</text> <text x="190" y="196" text-anchor="middle" fill="#9ca3af" font-size="11.5" font-family="monospace">id · cliente_id · fecha</text> <text x="190" y="240" text-anchor="middle" fill="#6b7280" font-size="11">2 tablas + JOIN para armar la respuesta</text>

<rect x="400" y="20" width="340" height="240" rx="12" fill="#111827" stroke="#374151"/> <text x="570" y="45" text-anchor="middle" fill="#34d399" font-weight="bold" font-size="15">MongoDB: documento</text> <rect x="430" y="65" width="280" height="150" rx="8" fill="#1f2937" stroke="#4b5563"/> <text x="445" y="85" fill="#6ee7b7" font-size="11" font-family="monospace">{</text> <text x="445" y="101" fill="#6ee7b7" font-size="11" font-family="monospace"> _id: 1,</text> <text x="445" y="117" fill="#6ee7b7" font-size="11" font-family="monospace"> empresa: "TechNova",</text> <text x="445" y="133" fill="#6ee7b7" font-size="11" font-family="monospace"> pedidos: [</text> <text x="445" y="149" fill="#6ee7b7" font-size="11" font-family="monospace"> { fecha: "2023-01-15" },</text> <text x="445" y="165" fill="#6ee7b7" font-size="11" font-family="monospace"> { fecha: "2023-03-15" },</text> <text x="445" y="181" fill="#6ee7b7" font-size="11" font-family="monospace"> // + 2 más</text> <text x="445" y="197" fill="#6ee7b7" font-size="11" font-family="monospace"> ] }</text> <text x="570" y="240" text-anchor="middle" fill="#6b7280" font-size="11">1 sola consulta: los pedidos ya vienen embebidos</text> </svg>

Ninguno de los dos "gana": la tabla normalizada evita repetir el nombre de la empresa en cada pedido (si TechNova cambia de nombre, lo cambias en un solo lugar); el documento evita el JOIN cuando casi siempre lees el cliente junto con sus pedidos. MongoDB elige duplicar datos a cambio de menos JOINs; SQL elige normalizar a cambio de nunca tener datos inconsistentes.

Los 4 tipos de NoSQL

No es una sola tecnología — es una familia con formas muy distintas de resolver el problema:

TipoMotores típicosPiensa en...
DocumentoMongoDB, CouchDBUn JSON grande por registro, con lo relacionado embebido adentro
Clave-valorRedis, DynamoDBUn diccionario gigante: pides una clave, te devuelve un valor
ColumnarCassandra, HBaseOptimizado para escribir muchísimo muy rápido (logs, sensores IoT)
GrafoNeo4j, ArangoDBNodos y relaciones como ciudadanos de primera clase — "amigos de amigos", recomendaciones

Clave-valor en acción: Redis

El más simple de los cuatro — no hay tablas, ni columnas, ni documentos anidados. Solo una clave (un texto) que apunta a un valor.

<svg viewBox="0 0 700 180" width="100%" height="auto" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Diagrama de un almacén clave-valor tipo Redis: el código pide GET usuario:42 y recibe el valor asociado a esa clave"> <defs> <marker id="arrow-nosql-2" markerWidth="8" markerHeight="8" refX="6" refY="4" orient="auto"> <path d="M0,0 L8,4 L0,8 Z" fill="#8b5cf6"/> </marker> </defs> <rect x="20" y="40" width="160" height="60" rx="10" fill="#1f2937" stroke="#4b5563"/> <text x="100" y="65" text-anchor="middle" fill="#ffffff" font-size="11.5">Tu código pide:</text> <text x="100" y="83" text-anchor="middle" fill="#a78bfa" font-size="12" font-family="monospace">GET usuario:42</text> <line x1="185" y1="70" x2="255" y2="70" stroke="#8b5cf6" stroke-width="2" marker-end="url(#arrow-nosql-2)"/> <rect x="260" y="20" width="420" height="140" rx="10" fill="#111827" stroke="#374151"/> <text x="470" y="45" text-anchor="middle" fill="#a78bfa" font-weight="bold" font-size="14">Redis: clave → valor</text> <rect x="285" y="58" width="370" height="22" rx="4" fill="#4c1d95" opacity="0.4"/> <text x="295" y="73" fill="#e9d5ff" font-size="11" font-family="monospace">usuario:42 → {"nombre":"Ana"}</text> <text x="295" y="100" fill="#d1d5db" font-size="11" font-family="monospace">carrito:42 → ["prod_1","prod_9"]</text> <text x="295" y="122" fill="#d1d5db" font-size="11" font-family="monospace">sesion:42 → "activa"</text> <text x="470" y="150" text-anchor="middle" fill="#6b7280" font-size="10.5">Una key → un valor. Sin JOINs, sin schema fijo.</text> </svg>

Por eso Redis es tan rápido: no hay que buscar entre filas ni evaluar un WHERE — con la clave exacta, el valor sale en tiempo constante. El costo: no puedes preguntar "¿qué usuarios tienen más de 3 productos en el carrito?" sin traerte cada carrito y revisarlo tú mismo. Redis se usa casi siempre como una capa de caché delante de una base relacional, no como reemplazo — guarda ahí lo que se lee muchísimo y cambia poco.

El mismo dato, en SQL

Esto SÍ corre — es la versión relacional exacta del documento de MongoDB de arriba, usando el dataset Nexus de todo el curso:

-- Lo mismo que el documento embebido de arriba, pero relacional: 2 tablas + JOIN
SELECT c.empresa, p.fecha
FROM clientes c
JOIN pedidos p ON p.cliente_id = c.id
WHERE c.id = 1
ORDER BY p.fecha;

Cheatsheet: ¿cuándo uso cuál?

Si tu problema es...Vas bien con...
Datos con relaciones claras, reportes, consistencia importaSQL (todo este curso)
Perfiles de usuario, catálogos con forma muy variable entre registrosDocumento (MongoDB)
Cachear resultados, sesiones, contadores en tiempo realClave-valor (Redis)
Escribir millones de eventos por segundo (logs, IoT, métricas)Columnar (Cassandra)
"Personas que le gustan las mismas películas que a ti"Grafo (Neo4j)
No sabes cuál — es tu primer proyectoEmpieza en SQL. Migra si un problema concreto lo pide.

---

← SQL en producción: conectar con Node.js

Ver todas las lecciones de Aprende SQL →