Host
Host sin encuesta en esta slide 0 envíos
Capacitación · 3 sesiones

Grafos, ontologías,
agentes de IA y prompt engineering

Sesión 1 de 3 · Criterio y modelado: pensar en caminos, no en joins

Joel Ibaceta
Día 1 de 3

01
Bloque 1 · 15 min · Demo + preguntas

El problema: ¿qué podemos preguntar?

Partimos de datos que ya conocen, y cambiamos la pregunta hasta que el modelo tabular deja de ser el instrumento natural.

Con qué vamos a trabajar

El modelo, en un diagrama entidad-relación

1N N1 1N 1N 1N 1N EMPLOYMENT customer_id FK company_id FK COMPANIES id PK name CUSTOMERS id PK name ACCOUNTS id PK customer_id FK balance TRANSFERS id PK from_account FK to_account FK amount PHONES customer_id FK phone
El tabular en su terreno

Preguntas que caben en una tabla

  • ¿Cuánto tiene Ana en total?
  • ¿Cuánto transfirió Pedro?
  • ¿Cuántos clientes tenemos? ¿Cuál es la transferencia promedio?
  • ¿Quién recibió dinero de Carlos?
Un valor, un agregado, un WHERE. El SQL sobre el lakehouse las resuelve directo, este es el terreno cómodo.
En vivo · el terreno cómodo Demo

¿Cuánto tiene Ana?

Cambiemos la pregunta

¿Qué conecta a Ana con Pedro?

No "¿qué valor tiene este registro?", sino "¿cómo están conectadas estas entidades?". La pregunta cambió de naturaleza.

Reconstruyendo relaciones

Tres caminos, todos ya en los datos

  • Ana → cuenta → transferencia → cuenta → Pedro
  • Ana → trabaja en → Acme ← trabaja en ← Pedro
  • Ana → comparte teléfono → Pedro
El SQL puede responderlo, con JOINs, y más JOINs. No es que no pueda; es que estamos expresando una pregunta sobre una red usando tablas.
¿Y si representamos directamente esas conexiones? En vivo

Esto es un grafo

La idea central del día
"Esa información quizá no existe como un registro. La estamos descubriendo al recorrer las relaciones."
Ana y Pedro comparten empresa: ese hecho no está guardado en ninguna fila. Lo descubrimos recorriendo el grafo.
Para que quede claro

No reemplazamos el lakehouse

Sigue igual

  • Los datos, el gobierno, SQL, ETL, ML.
  • Las preguntas de registros y agregaciones.

Agregamos

  • Otra forma de representar y recorrer relaciones.
  • Para cuando la pregunta tiene forma de red.
La pregunta no es "¿SQL puede?", es "¿qué estructura necesito para responder esto naturalmente?"
Antes de saltar al grafo…

Primero, veamos qué tan bien responde todo esto el modelo tabular.

Bloque 2 · Explorar la BD relacional, consulta a consulta.

02
Bloque 2 · 20 min · hands-on

Explorar la BD relacional

Antes de hablar de grafos, exprimamos lo que ya dominan: corramos SQL sobre el banco y respondamos lo tabular con soltura, hasta rozar el límite.

Cómo funciona este bloque · hands-on

Esto se corre, no se lee

  • Cada consulta vive en un lab embebido, se ejecuta en vivo.
  • Panel izquierdo: SQL. Panel derecho: el resultado que aparece.
  • Terreno cómodo: todo lo que sigue, el tabular lo responde sin despeinarse.
La pregunta incómoda llega al final, a propósito. Primero, confianza.
Consulta · agregación sobre transferencias En vivo

¿Cuánto transfirió Pedro?

El agregado es la casa del tabular

¿Cuántos clientes? ¿Cuánto es la transferencia promedio?

¿Cuántos clientes?

SELECT COUNT(*) AS n_clientes
FROM   banco_customers;
-- → 4

Transferencia promedio

SELECT ROUND(AVG(amount),2) AS promedio,
       COUNT(*)             AS n
FROM   banco_transfers;
-- → 600.00 · 4
Un conteo, una media. El lakehouse nace para esto y lo hace a escala de miles de millones de filas.
Consulta · seguir una arista dirigida En vivo

¿Quién recibió dinero de Carlos?

Cierre parcial · el terreno cómodo

Todo esto lo resolvió el tabular sin esfuerzo

Preguntas que encajan

  • ¿Cuánto tiene Ana? (valor)
  • ¿Cuánto transfirió Pedro? (suma)
  • ¿Cuántos clientes? (conteo)
  • ¿Quién recibió de Carlos? (un salto)

Lo que tienen en común

  • Profundidad fija y conocida.
  • El resultado es un valor o una fila.
  • Los joins se cuentan con los dedos.
Mientras el número de saltos sea conocido de antemano, el modelo tabular es imbatible. La pregunta es: ¿siempre lo es?
La pregunta rara

¿Qué relación hay entre Ana y Pedro?

No es un valor. Son varias relaciones, de tipos distintos: Ana transfiere a Pedro, ambos trabajan en Acme, y comparten teléfono.

El costo empieza a asomar · un JOIN por relación

En SQL: una consulta por cada tipo de vínculo

¿Comparten empleador?

SELECT co.name AS empresa
FROM   banco_employment e1
JOIN   banco_employment e2
       ON e2.company_id = e1.company_id
JOIN   banco_companies co
       ON co.id = e1.company_id
WHERE  e1.customer_id = 1   -- Ana
  AND  e2.customer_id = 2;  -- Pedro
-- → Acme

¿Comparten teléfono?

SELECT p1.phone
FROM   banco_phones p1
JOIN   banco_phones p2
       ON p2.phone = p1.phone
WHERE  p1.customer_id = 1   -- Ana
  AND  p2.customer_id = 2;  -- Pedro
-- → +51-900
Cada vínculo pide su propia consulta, con su self-join. ¿Y la transferencia? Otra más. Tres preguntas separadas para una pregunta humana.
Bloque 2 → Bloque 3

Un salto ya nos costó tres consultas. ¿Y si la pregunta crece un salto más, y no sabemos cuántos?

El problema no es el motor. Es la forma de la pregunta.

03
Bloque 3 · 15 min · reto

Cuando la pregunta se vuelve relacional

El SQL empezó a costar. Ahora subimos un salto y miramos qué pasa cuando la profundidad de la pregunta no se conoce de antemano.

Ampliemos el mapa · la gente también se conoce

Ana no solo tiene cuentas. Tiene gente alrededor

RELATIVE_OFRELATIVE_OF KNOWS Ana Rosamadre Sofíahermana Luisamigo Pedro
  • Sofía (hermana) y Luis (amigo): también son clientes del banco.
  • Rosa (madre): existe en el mundo de Ana, pero no es cliente.

cliente  ·  no cliente

Las mismas personas, dos capas: quién conoce a quién (familia, amistad) y quién mueve dinero. Subamos la pregunta de a poco.
Pregunta 1 · el calentamiento

¿Qué relación hay entre Ana y Pedro?

WORKS_ATHAS_PHONE Ana Pedro Acme +51-900

Dos caminos distintos, y ninguno lo dice una sola fila:

  • Comparten empleador (Acme).
  • Comparten teléfono (+51-900).
Un solo salto y ya son dos rutas de tipos distintos. En SQL: una consulta por cada una.
Pregunta 2 · relación + pertenencia

¿Qué amigos y familiares de Ana son clientes del banco?

Databricks SQL

SELECT p.name
FROM   banco_relations r
JOIN   banco_persons   p ON p.id = r.person_b
WHERE  r.person_a = 1            -- Ana
  AND  r.type IN ('RELATIVE_OF','KNOWS')
  AND  p.is_customer = 1;        -- "...en el banco"

Cypher

MATCH (ana:Customer {name:'Ana'})
      -[:RELATIVE_OF|KNOWS]-(p:Customer)
RETURN p.name

El label :Customer ya es el filtro "en el banco". Rosa es :Person, no :Customer → queda fuera sola.

Resultado: Sofía ✓ · Luis ✓ · Rosa ✗ (madre, no cliente). El filtro "en el banco" recién importa porque hay parientes que no lo son.
En vivo · córrela tú En vivo

Amigos y familia de Ana que son clientes

Pregunta 3 · la incómoda · partes relacionadas

¿Algún pariente de Ana le transfirió dinero, aunque sea indirectamente?

TRANSFERTRANSFER Sofíahermana Diana Ana
Sofía nunca le transfirió a Ana directamente: el dinero pasó por Diana. Dos vínculos en una pregunta (familia + dinero) y una profundidad que no conozco. Eso ya es un caso de partes relacionadas — y es justo donde el SQL se rinde.
Reto · una pregunta, sin pista de profundidad

¿Qué conecta a Ana con Carlos?

No comparten cuenta. No comparten empresa. Y sin embargo, algo los conecta.

La respuesta es un camino, no un valor

La cadena existe, pero nadie la guardó

Ana Acme Pedro Beta Carlos
Ningún registro dice "Ana está conectada con Carlos": emerge del recorrido. Y si Carlos estuviera a seis saltos, la pregunta sería la misma y no lo sabríamos de antemano.
El código crece con la pregunta

Un salto más = más JOINs

1 salto: compañeros de Ana

-- Ana ↔ compañeros de empresa
JOIN banco_employment e1 ...
JOIN banco_employment e2
     ON e2.company_id = e1.company_id
-- → Pedro (comparten Acme)

2 saltos: el compañero del compañero

JOIN banco_employment e1 ...  -- salto 1
JOIN banco_employment e2 ...
JOIN banco_employment e3 ...  -- salto 2
JOIN banco_employment e4 ...
-- → Carlos (Ana→Acme→Pedro→Beta→Carlos)
Cada salto pide otro par de JOINs. Solo funciona porque hardcodeamos "dos saltos". ¿Y si son 3? ¿6? ¿Y si no sé cuántos son?
Lo que NO estoy diciendo
No estoy diciendo que el SQL no pueda. Estoy diciendo que expresamos una pregunta sobre una red usando tablas.
El dato está ahí. El problema es la forma en que lo pedimos.
Databricks SQL · profundidad variable, hecha a mano

La vía correcta en SQL: WITH RECURSIVE

WITH RECURSIVE conexion AS (
  SELECT c.id, c.name,
         CAST(c.id AS CHAR(200)) AS camino,   -- path-tracking manual
         0                       AS saltos
  FROM   banco_customers c WHERE c.name = 'Ana'
  UNION ALL
  SELECT c2.id, c2.name,
         CONCAT(k.camino, '>', c2.id),
         k.saltos + 1
  FROM   conexion k
  JOIN   banco_employment e1 ON e1.customer_id = k.id
  JOIN   banco_employment e2 ON e2.company_id  = e1.company_id
  JOIN   banco_customers  c2 ON c2.id = e2.customer_id
  WHERE  LOCATE(c2.id, k.camino) = 0   -- cycle guard manual
    AND  k.saltos < 10                -- tope de saltos manual
)
SELECT name, camino, saltos FROM conexion WHERE name = 'Carlos';
En vivo · corramos el recursivo En vivo

¿Qué conecta a Ana con Carlos?

El costo oculto · andamiaje, no pregunta

Lo que tuve que escribir a mano

  • Cycle guard: sin él, un ciclo (A1→A2→A3→A1) cuelga la recursión.
  • Tope de saltos: un límite artificial, porque no sé la profundidad.
  • Path-tracking: reconstruir el camino paso a paso.
El WITH RECURSIVE no es el enemigo: sirve para profundidad acotada y auditable. Pero la mitad del código es andamiaje, no la pregunta.
Criterio de decisión · la rúbrica

¿Cuándo la pregunta es "de red"?

Es una pregunta de red si se cumple al menos uno:

  • Profundidad variable: no sé cuántos saltos hay de antemano.
  • Vínculos heterogéneos: empresa y cuenta y teléfono en la misma pregunta.
  • El camino es la respuesta: quiero la cadena, no un total.
"¿Qué conecta a Ana con Carlos?" marca las tres. Por eso el SQL peleó: no era una consulta mal escrita, era una pregunta de otra naturaleza.
Cierre del Bloque 3
El dato está ahí. No cambiamos los datos. ¿Y si cambiamos la forma?
La cadena Ana → Carlos ya existe. El SQL peleó por expresar una red con tablas, cargando cycle guards, topes y path-tracking a mano.
04
Bloque 4 · 20 min · hands-on

Construir el mismo modelo como grafo

No inventamos información nueva. Tomamos las mismas tablas del banco y las re-representamos: tabla a nodo, FK a arista. Las relaciones dejan de estar repartidas y se vuelven explícitas.

Re-representar, no re-capturar

El dato no cambia. Cambia cómo lo escribimos

Lo que NO hacemos

  • Capturar información nueva.
  • Cambiar las reglas de negocio.
  • Migrar o abandonar el lakehouse.

Lo que SÍ hacemos

  • Re-representar las mismas filas.
  • Hacer las relaciones explícitas.
  • Elegir la forma que la pregunta pide.
Mismo banco, mismos hechos. Solo cambia la estructura en la que los organizamos.
La herramienta de modelado · relacional → grafo

El diccionario de traducción

Relacional Grafo Nota Tabla Label (tipo de nodo) CUSTOMERS a Customer Fila Nodo una fila, un nodo Columna Propiedad de nodo o de arista FK / JOIN Arista (relación) de primera clase Tabla puente (M:N) Arista con propiedades o nodo si tiene > 2 FKs Surrogate key se descarta la identidad es el nodo
No es una tabla de referencia: es la lista de decisiones que tomarán en cada tabla que modelen.
El vocabulario mínimo, con el ejemplo del banco

Anatomía de un grafo: las cuatro piezas

  • Nodo: una entidad. Ana, la cuenta A1, Acme.
  • Arista: una relación de primera clase, con tipo y dirección. Ana OWNS A1.
  • Propiedad: atributo de nodo o de arista. name:"Ana", amount:1000.
  • Cardinalidad: cuántas aristas del tipo tocan un nodo (1:N, M:N).
OWNS Ana A1 name:"Ana" balance:5000

Nodo, arista con tipo, propiedades en ambos.

La respuesta · el dato cuelga de la flecha

El monto vive en la arista, no en el círculo

TRANSFER { amount: 1000 } A1 A2 balance:5000 balance:8000
El balance describe cada cuenta → vive en el nodo. El amount describe la transferencia → vive en la arista. En tabular no hay dónde colgarlo sin una tabla banco_transfers aparte.
Anatomía · el caso que conviene anticipar

Un cuidado: el supernodo

Un supernodo es un nodo con muchísimas aristas: un teléfono de call-center compartido por miles, una cuenta ómnibus.

No es un error de modelado, pero recorrerlo a ciegas es caro. Se anticipa: filtrar por tipo de arista, no expandir todo.

hub
La decisión de modelado que más pesa

¿Nodo propio o propiedad?

Propiedad

  • El atributo describe una sola entidad.
  • Nadie más lo comparte, nadie pregunta por él.
  • Ejemplo: balance de una cuenta.

Nodo

  • El atributo se comparte entre entidades.
  • Quiero recorrerlo, contarlo, llegar por él.
  • Ejemplo: el teléfono, promovido a Phone.
La prueba: si dos entidades pueden coincidir en ese valor y eso importa, es un nodo. Un valor colgado de una fila no conecta a nadie.
Cuatro movimientos que se repiten

Patrones de modelado de grafos

  • Tabla puente M:N → arista. Con propiedades si el vínculo las carga. EMPLOYMENT a WORKS_AT.
  • Reificar la relación. Si la relación tiene atributos propios, viven en la arista. amount en TRANSFER.
  • Atributo compartido → nodo. Teléfono, domicilio, IP: promoverlos revela conexiones que ninguna tabla guardaba.
  • Junction con > 2 FKs o vida propia → nodo intermedio. Cuando la relación deja de ser un enlace y pasa a ser una entidad.
El supernodo es el contrapeso: un atributo compartido por millones no siempre conviene hacerlo transitable a ciegas.
LAB de modelado · las cuatro tablas del guion

Cuatro tipos de nodo. Cuatro tipos de arista.

Nodos (labels)

Customer · Account · Company · Phone

Aristas (tipos)

OWNS · TRANSFER {amount} · WORKS_AT · HAS_PHONE

Vamos tabla por tabla. Ustedes proponen el mapeo, lo revelamos.
Modelado guiado · antes de revelar

Modélenlo ustedes. Cada tabla, una pregunta:

  • ¿Esta tabla es un nodo o una arista?
  • ¿Esta columna cuelga del nodo o de la arista?
  • ¿Este valor merece ser un nodo propio?
Modelen ustedes · en vivo En vivo

Modela una billetera virtual, tú lo dibujas

La propuesta · cómo lo modelaría

Una forma de modelar la billetera

CREATE (ana:Cliente {nombre:"Ana", saldo:1500}),
       (luis:Cliente {nombre:"Luis", saldo:300}),
       (sofia:Cliente {nombre:"Sofia", saldo:0})
CREATE (ana)-[:TIENE]->(:Tarjeta {n:"**41"})
CREATE (ana)-[:PAGA {monto:200}]->(:Comercio {nombre:"Spotify"})
CREATE (ana)-[:REFIRIO]->(luis),
       (luis)-[:REFIRIO]->(sofia)
El saldo describe a un cliente y nadie lo comparte → propiedad, no nodo. Un referido es una relación entre clientes → arista Cliente→Cliente, no un nodo. Así "referidos" se vuelve un recorrido.
Y ahora, la pregunta que el modelo habilita

¿Cuántos clientes llegaron por Ana?

Referidos, y los referidos de sus referidos, sin saber cuántos saltos.

MATCH (ana:Cliente {nombre:"Ana"})-[:REFIRIO*1..]->(c:Cliente)
RETURN count(DISTINCT c) AS clientes_por_ana

Resultado esperado: 2 → Luis (directo) y Sofía (referida de Luis).

La profundidad no se conoce de antemano (¿y si Sofía refiere a más?). En grafo es *1.. nativo; en Databricks SQL sería una CTE recursiva con sus guardas, justo lo del Bloque 3. Modelar bien habilita preguntar de red.
Córrelo en vivo En vivo

Clientes que llegaron por Ana, ejecutado

Paso 1 · tabla de entidades → nodos directos

CUSTOMERS → nodos Customer

CUSTOMERS idname 1Ana 2Pedro 3Carlos 4Diana
Ana Pedro Carlos Diana

Fila a nodo, name a propiedad.

El caso fácil: una tabla de entidades es una fábrica de nodos. El id se descarta, la identidad es el nodo.
Paso 2 · la FK deja de ser implícita

FK → arista OWNS, y la columna que se vuelve propiedad de la arista

ACCOUNTS idcustomer_idbalance A115000 A228000 TRANSFERS A1→A2 · amount 1000
OWNS TRANSFER 1000 Ana A1 A2

customer_id a OWNS, amount a propiedad de la arista.

La FK customer_id era un JOIN latente. Ahora es la arista OWNS. Y amount cuelga de la relación, no de un nodo.
Paso 3 · el junction desaparece como tabla

Tabla puente M:N → arista WORKS_AT

EMPLOYMENT customercompany AnaAcme PedroAcme PedroBeta CarlosBeta
Ana Pedro Carlos Acme Beta

Cada fila del puente a una arista WORKS_AT.

La tabla puente M:N no sobrevive como tabla: cada fila es una arista WORKS_AT. Pedro simplemente tiene dos.
LAB · las cuatro tablas, ya re-representadas

El grafo completo del banco básico

OWNS 1000500200 WORKS_AT HAS_PHONE Ana Pedro Carlos A1 A2 A3 Acme Beta +51-900

Coral = Customer · índigo = Account · charcoal = Company · ámbar = Phone. Ciclo A1→A2→A3→A1 visible. Ana y Pedro llegan al mismo teléfono.

En vivo · el banco entero, ahora como grafo En vivo

Recorrámoslo: que se dibuje solo

Modelado guiado · la decisión que abre el patrón

PHONES: ¿propiedad de Customer, o nodo propio?

Como propiedad

Ana lleva phone:"+51-900", Pedro lleva phone:"+51-900". Dos strings iguales, en dos nodos distintos. Para saber que coinciden hay que comparar valores, otra vez el JOIN.

Como nodo

Un solo nodo Phone {+51-900}. Ana y Pedro apuntan al mismo. La coincidencia deja de ser una comparación y pasa a ser estructura.

Decisión: es un atributo compartido y queremos llegar por él. Patrón "atributo compartido a nodo". Se convierte en Phone.
La relación que nunca almacenamos

El "comparte teléfono" no es una tabla

En ninguna parte guardamos "Ana y Pedro comparten teléfono". Guardamos dos hechos simples:

  • Ana HAS_PHONE +51-900
  • Pedro HAS_PHONE +51-900

El vínculo se descubre recorriendo:
Customer → Phone ← Customer

HAS_PHONE Ana Pedro +51-900

Dos aristas convergentes = un vínculo latente.

Esa información no existe como un registro. La descubrimos al recorrer las relaciones.
En vivo · el vínculo que nadie guardó En vivo

¿Quiénes comparten teléfono?

La misma verdad, dos formas

Antes y después, lado a lado

Modelo tabular

  • La relación es una FK que hay que unir.
  • Atributo de relación: tabla aparte.
  • "Comparten teléfono": JOIN de PHONES consigo misma.

Modelo de grafo

  • La relación es una arista explícita con nombre.
  • Atributo de relación: propiedad de la arista.
  • "Comparten teléfono": dos aristas que convergen.
Ni mejor ni peor: la relación pasó de implícita, reconstruida en la consulta, a explícita, parte del modelo.
Bloque 4 → Bloque 5

El mismo banco, ahora como grafo. Las relaciones dejaron de estar repartidas.

Ahora que son explícitas, preguntemos. En el Bloque 5 dejamos de dibujar el grafo y empezamos a recorrerlo.

Ahora ustedes · en Cypher Modela

Modela un supernodo

Ahora ustedes · en Cypher Modela

Modela el teléfono compartido

05
Bloque 5 · 30 min · hands-on

Consultar el grafo

Ya lo construimos, ahora preguntémosle. Las mismas preguntas de antes, pero esta vez recorriendo, no uniendo. Y una que en SQL nos costó, en una línea.

Cypher · un vistazo, no una clase de sintaxis

El patrón se lee como el dibujo

Un nodo entre paréntesis, una relación entre corchetes, una flecha para el sentido:

(a)-[:TRANSFER]->(b)

El mismo dibujo de la pizarra del Bloque 4, escrito en una línea.

:TRANSFER a b
Cypher es ASCII-art ejecutable. El diagrama que dibujarías en la pizarra es la consulta. La relación no se traduce a un JOIN: ya está escrita.
Cypher · consulta 0 · calentamiento En vivo

MATCH: describe la forma, el motor la busca

Contraste · el tabular sigue siendo trivial, y Cypher también

Mismo caso, dos lenguajes: ¿cuánto transfirió Ana?

Databricks SQL

SELECT SUM(t.amount)
FROM   banco_transfers t
JOIN   banco_accounts a  ON t.from_account = a.id
JOIN   banco_customers c ON a.customer_id = c.id
WHERE  c.name = 'Ana';

Cypher

MATCH (c:Customer {name:'Ana'})
      -[:OWNS]->(:Account)
      -[t:TRANSFER]->()
RETURN sum(t.amount)
Resultado idéntico: 1000. Para una pregunta tabular (un agregado, un valor), ambos responden de maravilla. Databricks no desaparece.
Contraste · aquí SQL empezaba a costar (Bloque 2) En vivo

¿Qué conecta a Ana con Pedro?, una línea

Lo que el patrón devolvió, sin que lo pidiéramos por nombre

Dos vínculos que no sabíamos que estaban

  • Ana → Acme → Pedro, trabajan juntos (WORKS_AT)
  • Ana → +51-900 → Pedro, comparten teléfono (HAS_PHONE)
No nombramos el tipo de arista (-[*1..2]-, sin :TIPO). Pedimos "cualquier conexión de 1 o 2 saltos", y el grafo devolvió las dos formas en que están conectados. Esa información no existe como registro: la descubrimos al recorrer.
Cypher · la pieza que lo cambia todo

El salto: caminos de longitud variable

-[:REL*1..]->

*1.. = de 1 a cuantos saltos hagan falta.

  • *1..2, hasta 2 saltos
  • *1.., profundidad no acotada
  • *, cualquier longitud

En Databricks (Bloque 3)

esto era un WITH RECURSIVE: caso base, paso recursivo, guarda de ciclos, tope de profundidad, reconstrucción del camino a mano.

No sabemos cuántos saltos hay, y no hace falta saberlo. El *1.. es exactamente la pregunta "¿están conectados, a la distancia que sea?".
Contraste directo · esto es lo que dolió en el Bloque 3

La prueba de fuego: ¿qué conecta a Ana con Carlos?

Databricks SQL · Bloque 3

WITH RECURSIVE conexion AS (
  SELECT c.id, c.name, 0 AS saltos
  FROM   banco_customers c WHERE c.name='Ana'
  UNION ALL
  SELECT c2.id, c2.name, k.saltos+1
  FROM   conexion k
  JOIN   banco_employment e1 ON e1.customer_id=k.id
  JOIN   banco_employment e2 ON e2.company_id=e1.company_id
  JOIN   banco_customers  c2 ON c2.id=e2.customer_id
  WHERE  k.saltos < 10        -- tope artificial
)  -- + cycle guard + path-tracking a mano
SELECT * FROM conexion WHERE name='Carlos';

Cypher, ahora

MATCH p = (a:Customer {name:'Ana'})
          -[:WORKS_AT*1..]-
          (c:Customer {name:'Carlos'})
RETURN p
ORDER BY length(p)
LIMIT 1

una línea de patrón · el camino es el resultado

Misma pregunta. A la izquierda, la recursión que dolió. A la derecha, *1... El camino Ana, Acme, Pedro, Beta, Carlos sale solo.
En vivo · la misma pregunta del Bloque 3, en una línea En vivo

¿Qué conecta a Ana con Carlos?

Cypher · el path como resultado de primera clase En vivo

El camino no es texto: es un dato

Lo que devuelve la consulta anterior

El camino lo recibimos, no lo reconstruimos

tel Ana Acme Pedro Beta Carlos
nodes(p)[Ana, Acme, Pedro, Beta, Carlos] · length(p)4. Ningún registro dice "Ana conecta con Carlos": el camino lo descubre, y lo devuelve como estructura, quién → quién → por qué.
Cypher · el patrón admite condiciones, como un WHERE En vivo

Filtrar y dirigir el recorrido

Recapitulación del bloque · antes del salto

Lo que acabamos de ver

Tabular, trivial en ambos

  • ¿Cuánto transfirió Ana? → 1000
  • Filtrar transferencias ≥ 500

Con forma de red, una línea

  • Ana↔Pedro: ambos vínculos, sin nombrarlos
  • Ana↔Carlos: *1.., camino completo
La misma consulta que en SQL crecía con cada salto, en Cypher no cambia de tamaño cuando cambia la profundidad. La forma de la consulta = la forma de la pregunta.
Bloque 5 → Bloque 6

Y ahora, las preguntas que en tabla ni intentábamos.

Ya no "¿cuánto?" ni "¿están conectados?". Ahora: ¿hay un anillo de transferencias? ¿quién comparte teléfono? ¿quién está a ≤2 saltos?

06
Bloque 6 · 25 min · ejercicio

Preguntas que explotan las relaciones

Hasta aquí tradujimos preguntas que la tabla también responde. Ahora vienen las que el grafo hace naturales, y la tabla sufre.

Ejercicio · lo que vamos a resolver

Tres preguntas incómodas

  1. ¿Ana y Pedro comparten algo que nadie registró?
  2. ¿El dinero vuelve al punto de partida?
  3. ¿Quién está cerca de Ana, sin saber a cuántos saltos?
Las tres corren lado a lado: Databricks SQLCypher. Y las tres tienen algo en común, que ninguna se contesta leyendo un registro.
Reto 1 · vínculo oculto (fraud ring)
No existe un registro "Ana–Pedro". Y sin embargo, están conectados.
Ambos apuntan al mismo teléfono +51-900. Ese vínculo no es una fila: es un patrón que aparece al recorrer.
Reto 1 · así se ve la conexión

Una estrella de teléfono

HAS_PHONE HAS_PHONE Ana +51-900 Pedro
Dos aristas que llegan al mismo nodo. En el grafo el teléfono es un nodo propio, no un atributo enterrado en la fila del cliente. La "coincidencia" es la estructura.
Reto 1 · Databricks SQL ↔ Cypher

El self-join contra la estrella

Databricks SQL · self-join

SELECT c1.name AS cliente_a,
       c2.name AS cliente_b,
       p1.phone
FROM   banco_phones p1
JOIN   banco_phones p2
       ON p1.phone = p2.phone
      AND p1.customer_id < p2.customer_id
JOIN   banco_customers c1 ON c1.id = p1.customer_id
JOIN   banco_customers c2 ON c2.id = p2.customer_id;

Cypher · el patrón se lee como el dibujo

MATCH (a:Customer)-[:HAS_PHONE]->(p:Phone)
        <-[:HAS_PHONE]-(b:Customer)
WHERE a.id < b.id
RETURN a.name, b.name, p.number;
El SQL puede, pero pide unir la tabla contra sí misma y el truco id < id para no contar el par dos veces. El Cypher es la estrella: (Customer)-[:HAS_PHONE]->(Phone)<-[:HAS_PHONE]-(Customer).
En vivo · el mismo resultado por dos caminos En vivo

¿Qué comparten Ana y Pedro que nadie escribió?

Reto 1 · lo que descubrimos
cliente_a cliente_b phone
Ana Pedro +51-900
"Esa información quizá no existe como un registro. La estamos descubriendo al recorrer las relaciones."
Carlos y Diana tienen números propios; no aparecen. El patrón solo dispara donde hay coincidencia real, y así se detectan los fraud rings por identidad compartida.
Reto 2 · flujo circular
A1 le transfiere a A2, A2 a A3… y A3 vuelve a A1. El dinero da la vuelta.
Un ciclo no se "ve" en la tabla de transferencias: solo se descubre siguiendo las transferencias hasta volver al inicio. En LA/FT, un flujo que da la vuelta es señal de alerta.
Reto 2 · así se ve el flujo circular

Un anillo de transferencias

1000 500 200 A1 A2 A3
El camino empieza y termina en A1. La longitud del anillo (aquí 3 saltos) es justo lo que en un caso real no conocemos de antemano: podría ser de 2, 3 o más cuentas.
Reto 2 · Databricks SQL ↔ Cypher

Recursión con guardas contra el patrón de ciclo

Databricks SQL · cierras el ciclo a mano

WITH RECURSIVE walk AS (
  SELECT from_account AS start,
         to_account   AS curr, 1 AS hops
  FROM   banco_transfers
  WHERE  from_account = 'A1'
  UNION ALL
  SELECT w.start, t.to_account, w.hops + 1
  FROM   walk w
  JOIN   banco_transfers t
         ON t.from_account = w.curr
  WHERE  w.hops < 10          -- tope artificial
    AND  w.curr <> w.start    -- corta el ciclo a mano
)
SELECT start AS cuenta, MIN(hops) AS saltos
FROM   walk WHERE curr = start GROUP BY start;

Cypher · un patrón de ciclo, longitud variable

MATCH path =
  (a:Account {id:'A1'})-[:TRANSFER*1..3]->(a)
RETURN a.id AS cuenta,
       length(path) AS saltos;
El *1..3 recorre profundidad variable; el nodo de inicio y fin es el mismo (a). El path viene "gratis".
El SQL puede, pero tú escribes el tope de saltos y la guarda de ciclo. En Cypher, "vuelve al mismo nodo" es el patrón: (a)-[:TRANSFER*]->(a).
En vivo · el andamiaje contra el patrón En vivo

¿El dinero vuelve a donde empezó?

Reto 2 · lo que descubrimos
cuenta saltos
A1 3

El anillo A1→A2→A3→A1 cierra en 3 saltos. A4→A1 existe, pero no forma ciclo: A1 nunca vuelve a A4. Señal de flujo circular (LA/FT).

"Esa información quizá no existe como un registro. La estamos descubriendo al recorrer las relaciones."
El "ciclo" no era ninguna fila; emergió de seguir las transferencias hasta volver al inicio.
Reto 3 · vecindario / vinculación
Todo lo que esté a uno o dos pasos de Ana, por la relación que sea.
No "¿quién le transfirió?" ni "¿quién trabaja con ella?". Preguntamos por proximidad, mezclando OWNS, TRANSFER, WORKS_AT y HAS_PHONE en una sola consulta.
Reto 3 · Databricks SQL ↔ Cypher

Los JOINs se multiplican por salto

Databricks SQL · una UNION por tipo de arista

WITH edges AS (        -- aristas de todo tipo
  SELECT customer_id AS src, id AS dst FROM banco_accounts
  UNION SELECT id, customer_id          FROM banco_accounts
  UNION SELECT from_account, to_account FROM banco_transfers
  UNION SELECT to_account, from_account FROM banco_transfers
  UNION SELECT customer_id, company_id  FROM banco_employment
  UNION SELECT company_id, customer_id  FROM banco_employment
  UNION SELECT customer_id, phone       FROM banco_phones
  UNION SELECT phone, customer_id       FROM banco_phones
)
SELECT DISTINCT e2.dst          -- + el vecindario de 1 salto
FROM   edges e1
JOIN   edges e2 ON e2.src = e1.dst
WHERE  e1.src = '1';            -- Ana

Cypher · profundidad variable, cualquier arista

MATCH (ana:Customer {name:'Ana'})
      -[*1..2]-(vecino)
RETURN DISTINCT vecino;
-[*1..2]- = cualquier relación, cualquier dirección, hasta 2 saltos. Un salto más = cambiar 2 por 3.
En SQL, cada tipo de vínculo es otra tabla en la UNION, y cada salto duplica el encadenado. En Cypher, "hasta 2 saltos por cualquier arista" es -[*1..2]-. Un salto más = un carácter más.
En vivo · el vecindario, a la vista En vivo

¿Quién está a ≤2 saltos de Ana?

Reto 3 · lo que descubrimos

El vecindario de Ana

A ≤ 2 saltos de Ana:

  • A1 (su cuenta) · Acme · +51-900 (1 salto)
  • Pedro (por Acme y por el teléfono) (2 saltos)
  • A2 · A3 · A4, las cuentas que tocan A1 (2 saltos)

Pedro llega por dos caminos distintos; el grafo lo devuelve una sola vez con DISTINCT.

Ana A1 Acme +51-900 Pedro A2 A4
El vecindario es la respuesta, y cada camino explica por qué alguien está cerca. Pedro está a 2 saltos por Acme y por el teléfono.
Síntesis del bloque

Donde la tabla suda, el grafo respira

Pregunta En Databricks SQL En Cypher
Teléfono compartido self-join + id<id ()-[:HAS_PHONE]->()<-[]-()
Ciclo de transferencias WITH RECURSIVE + guardas (a)-[:TRANSFER*]->(a)
Vecindario ≤ 2 saltos UNION de aristas × saltos -[*1..2]-
Las tres compartían algo: la respuesta no era un registro, era un patrón. Vínculo oculto, ciclo, vecindario, todos emergen al recorrer.
Transición · puente al cierre

No es cuál gana.
Es qué pregunta tienes delante.

¿Un valor, un agregado, un WHERE? Databricks.   ¿Un camino, un ciclo, un vecindario? Grafo.

Entonces, ¿cuándo cada uno?

07
Grafos en Databricks · 12 min · aterrizaje

Grafos en Databricks: no abandonamos el lakehouse

Vimos el poder del grafo: caminos, ciclos, teléfono compartido. Ahora la pregunta de ingeniero: ¿esto vive en nuestro stack? Sí, sobre las mismas tablas Delta.

Distinción sin descalificar

Databricks no es una base de datos de grafos

Databricks

Plataforma de datos y analytics.

  • Lakehouse, Delta.
  • SQL, Spark, ML.

Graph database

Diseñada alrededor de la conexión.

  • Nodos, relaciones.
  • Paths, traversal nativo.
No es "una mejor que otra". Son cosas distintas. Databricks organiza datos para analizarlos; una graph DB los organiza alrededor de cómo se conectan.
Nodos + aristas · sigue siendo Delta

Un grafo se puede escribir como dos tablas

Tabla nodes · las entidades

-- nodes (id, type, properties)
id    type      properties
c1    Customer  {name: "Ana"}
a1    Account   {balance: 5000}
co10  Company   {name: "Acme"}

Tabla edges · las relaciones

-- edges (source, relationship, target, properties)
source  relationship  target  properties
c1      OWNS          a1      {}
a1      TRANSFERS_TO  a2      {amount: 1000}
c1      WORKS_AT      co10    {}
Dos tablas Delta: una de nodos, una de aristas. Físicamente es el lakehouse de siempre; conceptualmente ya es un grafo.
GraphFrames sobre Spark · SÍNTESIS §6

Esas tablas se procesan como grafo

Recorrer

  • Paths, caminos de longitud variable.
  • Connected components, anillos de fraude.

Estructurar

  • Centrality, quién es pivote.
  • Motif finding, patrones estructurales.
# GraphFrames: el mismo dataset, otra vista
g = GraphFrame(nodes, edges)
g.find("(a)-[t]->(b); (b)-[t2]->(a)")   # motif: transferencia recíproca
Análisis de relaciones además del análisis tabular tradicional. Misma tabla Delta, dos vistas.
Un grafo no significa abandonar el lakehouse

La arquitectura, en un dibujo

DATOS DEL BANCO DATABRICKS lakehouse · Delta tabular data → SQL graph-shaped → graph analysis INSIGHTS
Un mismo origen, un mismo lakehouse, dos lentes: el tabular que ya usan y el de grafo. Ambos desembocan en insights.
De lo que dominan a lo nuevo · un paso a la vez

La progresión práctica del laboratorio

Tablas SQL Relaciones Nodes+Edges Grafo Paths / Patterns

Cada paso se apoya en el anterior. Y todo esto puede vivir sobre las tablas Delta que ya tienen.

La frase que se llevan
El grafo no necesariamente introduce nueva información. Hace explícita la estructura de relaciones que ya estaba implícita en nuestros datos.
Que Ana y Pedro comparten teléfono ya estaba en las tablas. El grafo solo lo vuelve explícito y recorrible.
El matiz honesto
No decimos que Databricks no pueda con relaciones. Cambiamos la forma de representarlas y explorarlas cuando la pregunta tiene estructura de red.
Mismo lakehouse. Mismo Delta. Otro lente para la pregunta que tiene forma de grafo.
Solo el mapa · no lo abrimos hoy

Y hay más de una vía

Databricks con nodes+edges es una forma. Existen otras, se ven en la próxima sesión:

  • Motor dedicado: recorrido interactivo (Kùzu, embebido).
  • Zero-ETL: consultar el Delta como grafo sin duplicarlo (PuppyGraph).
Ninguna exige migrar la fuente de verdad. Las comparamos en Sesión 2.
Transición · cierre

Ya sabemos que esto cabe en nuestro stack.

Cerremos con lo esencial: cuándo tabular y cuándo grafo.

08
Cierre · 10 min · discusión

Relacional vs. grafo

No cerramos con un ganador. Cerramos con un criterio: cada modelo hace naturales ciertas preguntas, y el trabajo del ingeniero es reconocer la forma de la suya.

Recap · de la tabla a la red

El arco del día, de un vistazo

la pregunta cambia la forma cambia Preguntas tabulares Databricks SQL Preguntas de red caminos, patrones Grafo
No cambiamos de herramienta por moda. Cambiamos cuando la pregunta cambió de naturaleza.
Honestidad de ingeniero · no se tira nada

Qué se conserva y qué cambia

Se conserva

  • Entidades, atributos, identificadores, valores.
  • Reglas de negocio.
  • Las relaciones que ya existían.

Cambia

  • Cómo organizamos y consultamos.
  • El peso de las relaciones.
  • La facilidad de explorar caminos.
  • Qué preguntas son naturales.
El grafo no inventa información: re-representa la que ya estaba en las tablas. El dato no se movió; cambió la forma de mirarlo.
El criterio, no la jerarquía

No es "malo vs. bueno": es la forma de la pregunta

Naturales en tabla

  • ¿Cuánto? ¿Cuántos?
  • ¿Quién recibió de Carlos?
  • Un valor, un agregado, un WHERE.

Naturales en grafo

  • ¿Qué conecta a Ana con Carlos?
  • ¿Hay un ciclo? ¿Quién comparte teléfono?
  • Caminos y patrones.
La pregunta no es "¿qué modelo es mejor?", sino "¿qué tipo de pregunta tengo?"
La idea del día
"Una base de datos no solo almacena información; también determina qué preguntas podemos expresar naturalmente."
La estructura en que guardamos los datos no es neutral: habilita unas preguntas y esconde otras.
Lo que se llevan
"Pensar en grafos es reconocer cuándo nuestra pregunta tiene forma de red."
No es un motor nuevo ni un lenguaje nuevo: es un cambio de mirada, antes de escribir la primera línea.
Lo que sigue · de la estructura al significado

Puente a la Sesión 2

Hoy vimos

  • La estructura: nodos y aristas.
  • Cómo recorrer relaciones y descubrir caminos.

Sesión 2

  • El significado: ontología y capa semántica.
  • Que todos hablemos del mismo qué.
Hoy aprendimos a recorrer las relaciones. En la próxima sesión les damos nombre y reglas compartidas.
Cierre de la Sesión 1

No se empieza por "necesito una base de datos de grafos".

Se empieza por: "¿qué forma tiene la pregunta que quiero hacer?"

Nos vemos en la Sesión 2.