DevTools

Cheatsheet Neo4j

Base de dados de grafos com linguagem de consulta Cypher

Volver a los lenguajes
Neo4j
72 tarjetas encontradas
Categorías:
Versiones:

Instalação e Setup


9 cards
Neo4j Desktop
# Windows/macOS/Linux:
# 1. Descargar en neo4j.com/download
# 2. Instalar Neo4j Desktop (gratis)
# 3. Crear un "Project"
# 4. "Add Database" > Create Local
#    Database (definir password)
# 5. Botón "Start"

# El Desktop incluye:
#   - Servidor Neo4j
#   - Neo4j Browser (interfaz)
#   - Bloom (visualización)

# Parar: botón "Stop"

Neo4j Desktop es la forma más simple (gratis para uso local). Crea un Project, añade una Local Database con password y haz clic en Start. Incluye el Neo4j Browser para consultas visuales.

Primera query
// Crear un nodo:
CREATE (p:Persona {nombre: "Ana", edad: 30})

// Ver todos los nodos:
MATCH (n) RETURN n

// Crear una relación:
CREATE (a:Persona {nombre: "Ray"})
MATCH (x:Persona {nombre: "Ana"}),
      (y:Persona {nombre: "Ray"})
CREATE (x)-[:AMIGA_DE]->(y)

// Consultar:
MATCH (a:Persona)-[:AMIGA_DE]->(b)
RETURN a.nombre, b.nombre

Flujo básico: CREATE crea nodos, MATCH búsqueda patrones y RETURN devuelve resultados. La sintaxis (nodos)-[relación]->(nodo) es el corazón de Cypher — dibuja el patrón que buscas.

Neo4j vs SQL
// SQL (tablas + JOINs):
// SELECT p.nombre, a.título
// FROM personas p
// JOIN amigos f ON f.persona_id = p.id
// JOIN personas a ON a.id = f.amigo_id
// (lento con muchos JOINs)

// Cypher (grafo nativo):
MATCH (p:Persona)-[:AMIGA_DE]->(a)
RETURN p.nombre, a.nombre

// Grafos: las relaciones son un "puntero
// físico" — recorrer es O(1)
// por salto, sin JOINs ni índices

En SQL, las relaciones viven en tablas de JOIN (el coste crece con la profundidad). En Neo4j, las relaciones son enlaces nativos — recorrer N saltos es rápido sin importar el tamaño total. Ideal para redes sociales, recomendaciones y grafos de conocimiento.

Neo4j Aura (cloud)
# Servicio gestionado en la cloud:
#   console.neo4j.io

# Aura Free (gratis para siempre):
#   200k nodos + 400k relaciones
#   1 instancia

# Pasos:
# 1. Crear cuenta
# 2. "Create Instance"
# 3. Elegir región y password
# 4. Guardar las credenciales
#    (¡mostradas solo una vez!)

# Conexión: neo4j+s://xxxx.databases.neo4j.io

Neo4j Aura es la versión cloud gestionada — el plan Free sirve para aprender (200k nodos). Crea la instancia en console.neo4j.io y guarda las credenciales (mostradas solo una vez). Conecta vía neo4j+s://.

Dataset de ejemplo (movies)
// En el Browser:
:play movies

// Carga un grafo de películas con:
//   Person, Movie (nodos)
//   ACTED_IN, DIRECTED (relaciones)

// Prueba:
MATCH (p:Person)-[:ACTED_IN]->(m:Movie)
RETURN p.name, m.title LIMIT 5

// ¿Quién dirigió "The Matrix"?
MATCH (d:Person)-[:DIRECTED]->
      (m:Movie {title: "The Matrix"})
RETURN d.name

:play movies carga un grafo de ejemplo (actores, películas, directores) perfecto para aprender. Usa MATCH con patrones como (p:Person)-[:ACTED_IN]->(m:Movie) para explorar relaciones reales.

Docker
# Correr Neo4j en Docker:
docker run \
  --name neo4j \
  -p 7474:7474 -p 7687:7687 \
  -v $HOME/neo4j/data:/data \
  -e NEO4J_AUTH=neo4j/password123 \
  neo4j:latest

# Puertos:
#   7474 → Neo4j Browser (HTTP)
#   7687 → Bolt (drivers/app)

# -v persiste los datos
# NEO4J_AUTH define user/password
# (sin esto, pide setup en el browser)

En Docker: expone los puertos 7474 (browser) y 7687 (Bolt para drivers). Define NEO4J_AUTH para user/password y monta un volumen en /data para persistir. Ideal para desarrollo.

Configuración (neo4j.conf)
# Fichero: conf/neo4j.conf
# (en el Desktop: Database > "..."
#  > Settings)

# Memoria (ajustar al hardware):
server.memory.heap.initial_size=1G
server.memory.heap.max_size=2G
server.memory.pagecache.size=1G

# Permitir conexiones remotas:
server.default_listen_address=0.0.0.0

# Reiniciar tras cambios:
# Database > Stop > Start

Configuraciones en conf/neo4j.conf: heap y pagecache controlan memoria (pagecache ≈ tamaño de los datos). default_listen_address=0.0.0.0 permite conexiones remotas. Reinicia el servicio tras los cambios.

Neo4j Browser
# Abrir: http://localhost:7474
# (o botón "Open" en el Desktop)

# Login:
#   Connect URL: bolt://localhost:7687
#   Username: neo4j
#   Password: (la tuya)

# Interfaz:
#   :help          → ayuda
#   :clear         → limpiar pantalla
#   :play movies   → dataset de ejemplo
#   Ctrl+Enter     → ejecutar query

# Resultados en grafo o tabla
# (botones Graph | Table | Text)

El Neo4j Browser (puerto 7474) es la interfaz de consultas. Comandos útiles: :help, :clear, :play movies (dataset de ejemplo). Ejecuta con Ctrl+Enter y ve resultados como grafo o tabla.

Usuarios y roles
// Cambiar password:
ALTER CURRENT USER
SET PASSWORD FROM "actual" TO "nueva"

// Crear usuario:
CREATE USER maria
SET PASSWORD "password123"
SET PASSWORD CHANGE NOT REQUIRED

// Dar roles:
GRANT ROLE reader TO maria
GRANT ROLE editor TO maria
GRANT ROLE admin TO maria

// Ver usuarios:
SHOW USERS

// Eliminar:
DROP USER maria

Gestiona usuarios con CREATE USER y roles (reader, editor, admin) vía GRANT ROLE. SHOW USERS los lista todos. Cambia tu password con ALTER CURRENT USER.

Criar Nós


9 cards
CREATE (nodo simple)
// Nodo sin label ni propiedades:
CREATE ()

// Nodo con label:
CREATE (:Persona)

// Nodo con propiedades:
CREATE (:Persona {
  nombre: "Ana",
  edad: 30,
  activa: true
})

// Paréntesis = nodo
// {} = propiedades (map)
// :Label = tipo/etiqueta

CREATE () crea un nodo. :Label le da un tipo (ej.: Persona) y {} define propiedades (map clave/valor). Los labels permiten filtrar en consultas — úsalos siempre.

Crear múltiples nodos
// Varios nodos en una query:
CREATE (:País {nombre: "Portugal"}),
       (:País {nombre: "España"}),
       (:País {nombre: "Francia"})

// Nodos ya enlazados:
CREATE (a:Persona {nombre: "Ana"})
       -[:SIGUE]->
       (b:Persona {nombre: "Ray"})

// Contar nodos creados:
CREATE (n:Prueba)
RETURN count(n)

Crea varios nodos separados por comas en un solo CREATE. Puedes crear nodos ya enlazados con la sintaxis de patrón completa — más eficiente que crear y enlazar en queries separadas.

UNWIND para bulk create
// Crear muchos nodos de una lista:
UNWIND ["Ana", "Ray", "Maria"] AS nombre
CREATE (:Persona {nombre: nombre})

// Con maps (varias propiedades):
UNWIND [
  {nombre: "Ana", edad: 30},
  {nombre: "Ray", edad: 25}
] AS datos
CREATE (p:Persona)
SET p = datos

// SET p = map define todas
// las propiedades de una vez

UNWIND transforma listas en filas — perfecto para crear muchos nodos en una query. Con maps, SET p = datos aplica todas las propiedades de una vez. Mucho más rápido que CREATEs separados.

Labels
// Un nodo puede tener VARIOS labels:
CREATE (n:Empleado:Gerente
        {nombre: "Ray"})

// Labels en CamelCase
// (convención: Persona, CuentaBancaria)

// Ver labels existentes:
CALL db.labels()

// Eliminar label:
MATCH (n:Gerente {nombre: "Ray"})
REMOVE n:Gerente

// Añadir label:
MATCH (n {nombre: "Ray"})
SET n:Admin

Los labels son etiquetas — un nodo puede tener varios (ej.: :Empleado:Gerente). Convención: CamelCase. CALL db.labels() los lista todos. Añade con SET n:Label, elimina con REMOVE n:Label.

MERGE (crear o encontrar)
// CREATE duplica si ya existe:
CREATE (:Persona {nombre: "Ana"})
CREATE (:Persona {nombre: "Ana"})  // ¡2 Anas!

// MERGE = "garantiza que existe":
MERGE (p:Persona {nombre: "Ana"})
ON CREATE SET p.creado = timestamp()
ON MATCH SET p.visto = timestamp()
RETURN p

// Si no existe → crea
// Si existe → usa el existente

// MERGE búsqueda TODO el patrón
// (label + propiedades dadas)

MERGE es el CREATE idempotente: búsqueda el patrón y solo lo crea si no existe — evita duplicados. ON CREATE SET corre en la creación, ON MATCH SET cuando ya existe. Esencial para imports.

Propiedades
// Tipos soportados:
CREATE (:Producto {
  nombre: "Portátil",     // String
  precio: 999.99,         // Double
  stock: 5,               // Integer
  disponible: true,       // Boolean
  tags: ["tech", "nuevo"], // Lista
  lanzamiento: date()     // Temporal
})

// Las propiedades NO pueden ser:
//   maps anidados, null
//   (null = propiedad ausente)

// Ver propiedades de un nodo:
MATCH (p:Producto) RETURN properties(p)

Las propiedades aceptan String, números, Boolean, listas y tipos temporales (date(), datetime()). No aceptan maps anidados ni null (null = propiedad inexistente). properties(n) las muestra todas.

MERGE con relaciones
// Garantizar persona Y relación:
MERGE (a:Persona {nombre: "Ana"})
MERGE (b:Persona {nombre: "Ray"})
MERGE (a)-[:AMIGA_DE]->(b)

// Error común: MERGE en el patrón
// completo sin que los nodos existan
// → crea todo de una vez (ok),
// pero lento en grafos grandes

// Mejor: MERGE nodos primero,
// después MERGE la relación

Usa un MERGE separado para cada nodo y después para la relación — garantiza que nada se duplica. Hacer MERGE del patrón completo funciona pero es más lento y menos explícito.

Variables de nodo
// Asignar el nodo a una variable:
CREATE (p:Persona {nombre: "Ana"})
RETURN p

// RETURN devuelve el nodo creado
// (con id interno y propiedades)

// Usar en multi-statement:
CREATE (a:Persona {nombre: "Ray"})
CREATE (b:Ciudad {nombre: "Lisbon"})
CREATE (a)-[:VIVE_EN]->(b)
RETURN a, b

// Las variables solo existen
// dentro de la query actual

Las variables (p, a, b) referencian nodos creados para usar en RETURN o en relaciones en la misma query. No persisten entre queries — para reutilizarlas, búsqueda con MATCH.

ID interno y elementId
// Cada nodo tiene un id interno:
MATCH (p:Persona)
RETURN id(p), p.nombre

// Neo4j 5+: elementId (String):
MATCH (p:Persona)
RETURN elementId(p), p.nombre

// ATENCIÓN: los ids se REUTILIZAN
// tras DELETE — ¡nunca los uses como
// clave externa permanente!

// Clave estable: crea tu propia
// propiedad (ej.: uuid, email)

id() devuelve el id interno (entero) y elementId() (Neo4j 5+) una string. Ambos se reutilizan tras DELETE — nunca sirven como clave permanente. Crea tu propia propiedad única (uuid, email).

Relações


9 cards
CREATE (relación)
// Enlazar dos nodos existentes:
MATCH (a:Persona {nombre: "Ana"}),
      (b:Persona {nombre: "Ray"})
CREATE (a)-[:AMIGA_DE]->(b)

// Sintaxis:
//   (origen)-[:TIPO]->(destino)
//   -[:TIPO]->  = dirección →
//   <-[:TIPO]-  = dirección ←
//   -[:TIPO]-   = sin dirección

// TIPO en MAYUSCULAS_CON_UNDERSCORE
// (convención: AMIGA_DE, TRABAJA_EN)

Las relaciones se crean con (a)-[:TIPO]->(b) — la flecha define la dirección. Los tipos siguen la convención MAYUSCULAS_CON_UNDERSCORE. Haz MATCH de los nodos primero y después CREATE de la relación.

Patrones de relación
// Filtrar por tipo:
MATCH (a)-[:AMIGA_DE]->(b)
RETURN a, b

// Varios tipos (OR):
MATCH (a)-[:AMIGA_DE|COLEGA]->(b)
RETURN a, b

// Con label en el destino:
MATCH (p:Persona)-[:COMPRO]->
      (pr:Producto)
RETURN p.nombre, pr.nombre

// Relación sin tipo (cualquiera):
MATCH (a)-[r]->(b)
RETURN type(r), count(*)

Patrones flexibles: [:TIPO1|TIPO2] casa varios tipos (OR), los labels en el destino filtran nodos, y [r] sin tipo casa cualquier relación. Combina con count(*) para analizar la distribución de tipos.

Relaciones self-loop y multi
// Self-loop (nodo enlazado a sí mismo):
MATCH (p:Persona {nombre: "Ana"})
CREATE (p)-[:MENTOR_DE]->(p)

// Múltiples relaciones del mismo tipo
// entre los mismos nodos (permitido):
MATCH (a {nombre: "Ana"}), (b {nombre: "Ray"})
CREATE (a)-[:PAGO {valor: 10}]->(b)
CREATE (a)-[:PAGO {valor: 20}]->(b)

// Consultar ambas:
MATCH (a {nombre: "Ana"})-[r:PAGO]->(b)
RETURN r.valor   // 2 filas

Neo4j permite self-loops (un nodo enlazado a sí mismo) y múltiples relaciones del mismo tipo entre el mismo par de nodos (ej.: varios pagos). Cada relación es una entidad independiente con sus propias propiedades.

Propiedades en relaciones
// Las relaciones también tienen propiedades:
MATCH (a:Persona {nombre: "Ana"}),
      (m:Movie {title: "Matrix"})
CREATE (a)-[:CALIFICO {
  nota: 5,
  fecha: date(),
  comentario: "¡Excelente!"
}]->(m)

// Leer propiedades:
MATCH (a)-[r:CALIFICO]->(m)
RETURN a.nombre, r.nota, m.title

// Variable de la relación: [r:TIPO]

Las relaciones aceptan propiedades como los nodos — ideal para metadatos (nota, fecha). Captura la relación en una variable con [r:TIPO] para acceder a sus propiedades en RETURN.

Caminos de longitud variable
// Amigos de amigos (2 saltos):
MATCH (a:Persona {nombre: "Ana"})
      -[:AMIGA_DE]->()-[:AMIGA_DE]->(b)
RETURN b.nombre

// Con longitud variable:
MATCH (a {nombre: "Ana"})
      -[:AMIGA_DE*1..3]->(b)
RETURN DISTINCT b.nombre

// *1..3 = 1 a 3 saltos
// *2    = exactamente 2
// *..5  = hasta 5
// *     = cualquiera (¡cuidado!)

// DISTINCT evita duplicados

-[:TIPO*1..3]-> recorre caminos de longitud variable (1 a 3 saltos) — la gran ventaja de los grafos. Usa DISTINCT para evitar duplicados y limita siempre el máximo (los caminos sin límite pueden explotar).

Dirección
// Crear con dirección:
CREATE (a)-[:SIGUE]->(b)    // a sigue b

// Consultar con dirección:
MATCH (a:Persona)-[:SIGUE]->(b)
RETURN a.nombre, b.nombre

// Consultar SIN dirección
// (encuentra ambos sentidos):
MATCH (a:Persona)-[:SIGUE]-(b)
RETURN a.nombre, b.nombre

// La dirección SIEMPRE se guarda
// (no existe relación "sin dirección"
//  — solo la ignoras en la query)

La dirección siempre se almacena-[]-> crea/consulta en un sentido, -[]- ignora la dirección en la consulta (encuentra ambos). Elige la dirección al modelar (ej.: SIGUE tiene dirección natural).

shortestPath
// Camino más corto entre 2 nodos:
MATCH (a:Persona {nombre: "Ana"}),
      (z:Persona {nombre: "Zé"}),
      camino = shortestPath(
        (a)-[:AMIGA_DE*..10]-(z)
      )
RETURN camino

// Nodos del camino:
RETURN nodes(camino)

// Relaciones del camino:
RETURN relationships(camino)

// Longitud:
RETURN length(camino)

shortestPath() encuentra el camino mínimo entre dos nodos (algoritmo BFS nativo). Extrae detalles con nodes(), relationships() y length(). Limita con *..10 para performance.

Varios tipos de relación
// Crear relaciones diferentes:
MATCH (a:Persona {nombre: "Ana"}),
      (e:Empresa {nombre: "TechCorp"})
CREATE (a)-[:TRABAJA_EN {desde: 2020}]->(e)

MATCH (a:Persona {nombre: "Ana"}),
      (c:Ciudad {nombre: "Porto"})
CREATE (a)-[:VIVE_EN]->(c)

// Consultar cualquiera:
MATCH (a:Persona {nombre: "Ana"})-[r]->()
RETURN type(r)

// type(r) devuelve el nombre del tipo

Un nodo puede tener relaciones de varios tipos (TRABAJA_EN, VIVE_EN...). [r]->() casa cualquier relación saliente; type(r) devuelve el tipo como string. () sin label casa cualquier nodo.

Eliminar relaciones
// Eliminar una relación específica:
MATCH (a:Persona {nombre: "Ana"})
      -[r:AMIGA_DE]->(b {nombre: "Ray"})
DELETE r

// Eliminar TODAS las relaciones de un nodo:
MATCH (p:Persona {nombre: "Ana"})-[r]-()
DELETE r

// El nodo sigue existiendo
// (solo se eliminan las relaciones)

// Eliminar nodo Y relaciones:
MATCH (p:Persona {nombre: "Ana"})
DETACH DELETE p

Elimina relaciones con DELETE r (el nodo se mantiene). MATCH (p)-[r]-() + DELETE r quita todos los enlaces de un nodo. Para eliminar un nodo con relaciones usa DETACH DELETE (si no, da error).

Consultas (MATCH)


9 cards
MATCH básico
// Todos los nodos:
MATCH (n) RETURN n

// Todos los nodos con label:
MATCH (p:Persona) RETURN p

// Con propiedades:
MATCH (p:Persona {nombre: "Ana"})
RETURN p

// Varias condiciones:
MATCH (p:Persona {nombre: "Ana", edad: 30})
RETURN p

// MATCH no crea nada —
// solo búsqueda patrones existentes

MATCH búsqueda patrones en el grafo (nunca crea). Filtra por label (:Persona) y propiedades ({nombre: "Ana"}). Sin patrones, devuelve todos los nodos — limita siempre con WHERE/LIMIT.

OPTIONAL MATCH
// El LEFT JOIN de Cypher:
MATCH (p:Persona)
OPTIONAL MATCH (p)-[:TIENE]->(c:Coche)
RETURN p.nombre, c.modelo

// Las personas SIN coche aparecen
// con c.modelo = null

// Comparar:
// MATCH normal → solo personas
//   con coche (inner join)
// OPTIONAL MATCH → todas las
//   personas (left join)

OPTIONAL MATCH es el LEFT JOIN de Cypher: devuelve resultados incluso cuando el patrón no casa (con null en las variables no casadas). El MATCH normal equivale a INNER JOIN.

collect (listas)
// Juntar valores en una lista:
MATCH (p:Persona)
RETURN collect(p.nombre)

// Amigos de cada persona:
MATCH (p:Persona)-[:AMIGA_DE]->(a)
RETURN p.nombre, collect(a.nombre) AS amigos

// Tamaño de la lista:
RETURN p.nombre,
       size(collect(a.nombre)) AS n_amigos

// collect() nunca devuelve null
// (lista vacía si nada casa)

collect() agrega valores en una lista — perfecto para "todos los amigos de cada persona". size() da la longitud. Nunca devuelve null (lista vacía sí), al contrario de otras agregaciones.

WHERE
MATCH (p:Persona)
WHERE p.edad > 25
  AND p.activa = true
RETURN p.nombre

// Operadores: =, <>, <, >, <=, >=
// Lógicos: AND, OR, NOT

// Existencia de propiedad:
WHERE p.email IS NOT NULL

// IN:
WHERE p.ciudad IN ["Porto", "Lisbon"]

// String:
WHERE p.nombre STARTS WITH "An"
WHERE p.nombre CONTAINS "ari"
WHERE p.nombre ENDS WITH "na"

WHERE filtra con operadores (>, <>), AND/OR/NOT, IN para listas y IS NOT NULL para existencia. Strings: STARTS WITH, CONTAINS, ENDS WITH (y regex con =~).

Patrones complejos
// Triángulo de amistad:
MATCH (a:Persona)-[:AMIGA_DE]->(b),
      (b)-[:AMIGA_DE]->(c),
      (c)-[:AMIGA_DE]->(a)
RETURN a.nombre, b.nombre, c.nombre

// Persona que compró el mismo
// producto que otra:
MATCH (p1)-[:COMPRO]->(pr)<-[:COMPRO]-(p2)
WHERE p1 <> p2
RETURN p1.nombre, p2.nombre, pr.nombre

// Reutilizar variables crea
// el enlace entre patrones

Los patrones complejos se conectan reutilizando variables (b aparece en dos relaciones; pr es compartido). WHERE p1 <> p2 se excluye a sí mismo. Así es como se expresan triángulos, recomendaciones y caminos.

RETURN
// Nodos completos:
MATCH (p:Persona) RETURN p

// Propiedades específicas:
RETURN p.nombre, p.edad

// Alias:
RETURN p.nombre AS nombre,
       p.edad AS "Años de edad"

// Expresiones:
RETURN p.nombre, p.edad * 2

// Todo:
MATCH (p:Persona) RETURN *

// Distintos:
RETURN DISTINCT p.ciudad

RETURN define el output: nodos completos, propiedades, aliases (AS) o expresiones. RETURN * devuelve todas las variables. DISTINCT elimina duplicados de los resultados.

WHERE con relaciones
// Filtrar por EXISTENCIA de patrón:
MATCH (p:Persona)
WHERE EXISTS {
  (p)-[:TIENE]->(:Coche)
}
RETURN p.nombre

// O negación (sin coche):
MATCH (p:Persona)
WHERE NOT EXISTS {
  (p)-[:TIENE]->(:Coche)
}
RETURN p.nombre

// EXISTS {} = subquery de
// existencia (Neo4j 4.4+)

EXISTS { patrón } filtra nodos por la existencia de un patrón (sin devolverlo). NOT EXISTS hace la negación — "personas sin coche". Reemplaza subqueries de existencia de SQL.

ORDER BY, SKIP, LIMIT
// Ordenar:
MATCH (p:Persona)
RETURN p.nombre, p.edad
ORDER BY p.edad DESC

// Paginación:
MATCH (p:Persona)
RETURN p.nombre
ORDER BY p.nombre
SKIP 10        // saltar 10
LIMIT 5        // traer 5

// Top 3 más viejos:
MATCH (p:Persona)
RETURN p.nombre, p.edad
ORDER BY p.edad DESC
LIMIT 3

ORDER BY ordena (DESC para descendente). SKIP + LIMIT hacen paginación. Combina los tres para top-N: ORDER BY ... DESC LIMIT 3.

Contar y agregar
// Contar nodos:
MATCH (p:Persona) RETURN count(p)

// Contar relaciones:
MATCH ()-[r:AMIGA_DE]->()
RETURN count(r)

// Agrupar (GROUP BY implícito):
MATCH (p:Persona)
RETURN p.ciudad, count(p) AS total
ORDER BY total DESC

// Media, suma, min, max:
MATCH (p:Persona)
RETURN avg(p.edad),
       min(p.edad),
       max(p.edad),
       sum(p.edad)

La agregación es implícita: count(), avg(), sum(), min(), max(). El agrupamiento se hace por las columnas no agregadas (como un GROUP BY automático). count(r) cuenta relaciones.

Atualizar e Apagar


9 cards
SET (actualizar)
// Actualizar una propiedad:
MATCH (p:Persona {nombre: "Ana"})
SET p.edad = 31
RETURN p

// Varias propiedades:
MATCH (p:Persona {nombre: "Ana"})
SET p.edad = 31,
    p.ciudad = "Porto",
    p.actualizado = timestamp()

// Añadir una propiedad nueva:
MATCH (p:Persona {nombre: "Ana"})
SET p.email = "ana@mail.com"

// SET solo actualiza nodos existentes
// (MATCH tiene que encontrarlos)

SET actualiza propiedades en nodos encontrados vía MATCH. Sirve para cambiar valores, añadir propiedades nuevas y múltiples campos de una vez. No crea nada — el nodo tiene que existir.

MERGE ON CREATE / ON MATCH
// El upsert de Cypher:
MERGE (p:Persona {email: "ana@mail.com"})
ON CREATE SET
  p.nombre = "Ana",
  p.creado = datetime(),
  p.nuevo = true
ON MATCH SET
  p.último_login = datetime()
RETURN p

// Primera vez: crea con todo
// Siguientes: solo actualiza
//   último_login

// Patrón esencial para imports
// y sincronizaciones

El upsert de Neo4j: MERGE + ON CREATE SET (solo en la creación) + ON MATCH SET (solo si ya existe). Perfecto para imports idempotentes — corre la misma query varias veces sin duplicar.

Transacciones explícitas
// En el Browser, todo es transacción.
// En drivers (ej.: Python):

// BEGIN
//   CREATE (:Persona {nombre: "Ana"})
// COMMIT

// O rollback:
// BEGIN
//   CREATE (:Persona {nombre: "Error"})
// ROLLBACK   // deshace

// En el Browser:
// :begin  → inicia transacción
// :commit → confirma
// :rollback → deshace

Las transacciones garantizan atomicidad: BEGIN + COMMIT confirma, ROLLBACK deshace. En el Browser usa :begin/:commit. En drivers, usa las APIs transaccionales (nunca dejes transacciones abiertas).

SET con map (+= y =)
// = sustituye TODAS las props:
MATCH (p:Persona {nombre: "Ana"})
SET p = {nombre: "Ana", edad: 31}
// (¡email, ciudad... borrados!)

// += hace MERGE de las props:
MATCH (p:Persona {nombre: "Ana"})
SET p += {edad: 31, ciudad: "Porto"}
// (otras props mantenidas)

// Regla:
//   SET p = map   → sustituye todo
//   SET p += map  → actualiza solo
//                    las dadas

Diferencia crítica: SET p = map sustituye todas las propiedades (las omitidas se borran); SET p += map solo actualiza las dadas, manteniendo el resto. Usa += para updates parciales.

Actualizar relaciones
// SET en relación:
MATCH (a:Persona {nombre: "Ana"})
      -[r:CALIFICO]->(m:Movie)
SET r.nota = 4,
    r.revisado = date()
RETURN r

// Añadir propiedad:
MATCH ()-[r:AMIGA_DE]->()
SET r.confirmada = true

// Eliminar propiedad:
MATCH ()-[r:CALIFICO]->()
REMOVE r.comentario

Las relaciones se actualizan igual que los nodos: captura con [r:TIPO] y usa SET r.prop = valor o REMOVE r.prop. Se aplican las mismas reglas de += y null.

REMOVE
// Eliminar propiedad:
MATCH (p:Persona {nombre: "Ana"})
REMOVE p.email
RETURN p

// Eliminar label:
MATCH (p:Gerente {nombre: "Ray"})
REMOVE p:Gerente

// Eliminar varias:
MATCH (p:Persona {nombre: "Ana"})
REMOVE p.email, p.telefono

// Alternativa: SET p.email = null
// (mismo efecto — quita la prop)

REMOVE elimina propiedades o labels de un nodo. SET p.email = null tiene el mismo efecto (las propiedades null no existen en Neo4j). Útil para limpiar datos obsoletos.

FOREACH
// Actualizar varios nodos de una lista:
MATCH (p:Persona)
WHERE p.ciudad = "Lisbon"
FOREACH (x IN collect(p) |
  SET x.region = "Gran Lisbon"
)

// Útil en caminos:
MATCH camino = (a)-[*1..3]->(b)
FOREACH (n IN nodes(camino) |
  SET n.visitado = true
)

// FOREACH itera listas y
// ejecuta SET/CREATE/MERGE/DELETE

FOREACH (x IN lista | SET ...) itera listas para actualizar múltiples elementos — útil en nodes(camino) para marcar todos los nodos de un camino. Soporta SET, CREATE, MERGE y DELETE.

DELETE y DETACH DELETE
// Eliminar nodo SIN relaciones:
MATCH (p:Persona {nombre: "Prueba"})
DELETE p

// ¡Error si el nodo tiene relaciones!
// (Neo4j protege la integridad)

// Eliminar nodo CON relaciones:
MATCH (p:Persona {nombre: "Prueba"})
DETACH DELETE p
// (elimina las relaciones primero)

// Eliminar TODO (¡cuidado!):
MATCH (n) DETACH DELETE n

DELETE elimina nodos sin relaciones — si tiene alguna, da error (protección de integridad). DETACH DELETE elimina el nodo y todas sus relaciones. MATCH (n) DETACH DELETE n limpia la BD entera — usa con cuidado.

Eliminar en lote (seguro)
// LENTO (transacción gigante):
// MATCH (n) DETACH DELETE n

// RÁPIDO (lotes de 10k):
MATCH (n:Log)
WITH n LIMIT 10000
DETACH DELETE n

// Repetir hasta que devuelva 0
// (o en script: while affected > 0)

// Eliminar relaciones en lote:
MATCH ()-[r:TEMPORAL]->()
WITH r LIMIT 10000
DELETE r

Eliminar millones de nodos de una vez bloquea la BD. Hazlo por lotes: WITH n LIMIT 10000 DETACH DELETE n y repite hasta que afecte 0. Cada lote es una transacción pequeña — no agota memoria.

Índices e Constraints


9 cards
CREATE INDEX
// Índice en una propiedad:
CREATE INDEX persona_nombre
FOR (p:Persona) ON (p.nombre)

// Compuesto (varias props):
CREATE INDEX persona_nombre_edad
FOR (p:Persona) ON (p.nombre, p.edad)

// En relaciones (Neo4j 5+):
CREATE INDEX califico_nota
FOR ()-[r:CALIFICO]-() ON (r.nota)

// Los índices aceleran MATCH/WHERE
// pero cuestan en writes

CREATE INDEX ... FOR (n:Label) ON (n.prop) acelera búsquedas por esa propiedad. Soporta índices compuestos (varias props) y en relaciones. Trade-off: los writes se vuelven más lentos — indexa solo lo necesario.

SHOW y DROP
// Listar índices:
SHOW INDEXES

// Listar constraints:
SHOW CONSTRAINTS

// Detalles de un índice:
SHOW INDEXES WHERE name = "persona_nombre"

// Borrar índice:
DROP INDEX persona_nombre

// Borrar constraint:
DROP CONSTRAINT persona_email_unica

// Los nombres aparecen en la
// columna "name" de los resultados

SHOW INDEXES y SHOW CONSTRAINTS listan todo (con nombres y estado). Elimina con DROP INDEX nombre / DROP CONSTRAINT nombre. Verifica siempre antes de crear duplicados.

Reglas de rendimiento
// 1. Indexa props de MATCH/WHERE
// 2. Usa labels (nunca MATCH (n)
//    sin filtro en BDs grandes)
// 3. LIMIT pronto:
MATCH (p:Persona)
WHERE p.ciudad = "Porto"
RETURN p LIMIT 100

// 4. Patrones selectivos primero:
//    (empieza por el nodo más raro)
// 5. Evita * sin límite máximo
// 6. PROFILE en las queries lentas

Reglas de oro: indexa propiedades filtradas, empieza patrones por los nodos más selectivos, usa LIMIT pronto, limita caminos variables y valida con PROFILE. Grafos rápidos = patrones bien diseñados.

Constraint de unicidad
// Garantizar email único:
CREATE CONSTRAINT persona_email_unica
FOR (p:Persona)
REQUIRE p.email IS UNIQUE

// Intentar duplicar → ERROR:
CREATE (:Persona {email: "a@b.com"})
CREATE (:Persona {email: "a@b.com"})
// ConstraintValidationFailed

// Los constraints crean un índice
// automáticamente (¡no dupliques!)

REQUIRE p.email IS UNIQUE impide duplicados — la segunda inserción falla con error. El constraint crea un índice automático (no crees otro igual). Fundamental para claves de negocio (email, uuid).

Índices fulltext
// Índice de texto completo:
CREATE FULLTEXT INDEX busqueda_nombre
FOR (p:Persona|Empresa)
ON EACH [p.nombre, p.descripcion]

// Buscar (con score):
CALL db.index.fulltext.queryNodes(
  "busqueda_nombre", "ana silva"
)
YIELD node, score
RETURN node.nombre, score

// Soporta: AND, OR, NOT,
// "frases", wildcards*

Los índices fulltext permiten búsqueda por texto en varias propiedades/labels con score de relevancia. Consulta vía db.index.fulltext.queryNodes(). Soporta operadores booleanos y wildcards.

Constraint de existencia
// Propiedad obligatoria:
CREATE CONSTRAINT persona_nombre
FOR (p:Persona)
REQUIRE p.nombre IS NOT NULL

// Crear sin nombre → ERROR:
CREATE (:Persona {edad: 30})
// falla (falta nombre)

// Label obligatorio (Neo4j 5+):
CREATE CONSTRAINT tiene_label
FOR (p:Persona)
REQUIRE p:Activa IS NOT NULL

REQUIRE p.nombre IS NOT NULL vuelve la propiedad obligatoria — las inserciones sin ella fallan. Garantiza calidad de datos en el origen (Enterprise para algunas; los existence constraints varían por versión).

Índices point y range
// Índice espacial (geo):
CREATE INDEX punto_local
FOR (l:Local) ON (l.localizacion)

// Crear punto:
CREATE (l:Local {
  nombre: "Sede",
  localizacion: point({
    latitude: 41.15, longitude: -8.61
  })
})

// Consultar por distancia:
MATCH (l:Local)
WHERE distance(l.localizacion,
  point({latitude: 41.16,
         longitude: -8.60})) < 5000
RETURN l.nombre   // < 5 km

Los índices point aceleran queries geoespaciales. Crea coordenadas con point({latitude, longitude}) y mide distancias con distance() (metros). Útil para "cerca de mí".

Node Key
// Clave primaria compuesta:
CREATE CONSTRAINT persona_key
FOR (p:Persona)
REQUIRE (p.nombre, p.fecha_nac)
IS NODE KEY

// NODE KEY = UNIQUE + NOT NULL
// en todas las propiedades

// Error si falta una:
CREATE (:Persona {nombre: "Ana"})
// falla (falta fecha_nac)

// Error si duplicada:
// (mismo nombre + fecha_nac)

NODE KEY es la clave primaria de Neo4j: combina unicidad + NOT NULL en varias propiedades. Ideal para claves compuestas de negocio (ej.: nombre + fecha de nacimiento).

EXPLAIN y PROFILE
// Ver el plan SIN ejecutar:
EXPLAIN MATCH (p:Persona)
WHERE p.nombre = "Ana" RETURN p

// Ver el plan CON métricas reales:
PROFILE MATCH (p:Persona)
WHERE p.nombre = "Ana" RETURN p

// Qué buscar:
//   NodeByLabelScan → ¡lento!
//     (falta índice)
//   NodeIndexSeek → rápido ✅
//   db hits → cuanto menor mejor

EXPLAIN muestra el plan de ejecución sin correr; PROFILE corre y da métricas reales (db hits, filas). NodeIndexSeek = usa índice (bueno); NodeByLabelScan = recorre todo (¡crea índice!).

Funções e APOC


9 cards
Funciones de string
RETURN upper("ana")        // "ANA"
RETURN lower("ANA")        // "ana"
RETURN trim("  hola  ")    // "hola"
RETURN size("hola")         // 4
RETURN substring("Neo4j", 0, 3)  // "Neo"
RETURN replace("a-b", "-", "+")  // "a+b"
RETURN split("a,b,c", ",") // ["a","b","c"]
RETURN left("Neo4j", 3)    // "Neo"
RETURN right("Neo4j", 2)   // "4j"

// Concatenar:
RETURN "Hola " + "Mundo"

Funciones de string: upper/lower/trim, substring, replace, split (devuelve una lista), left/right. Concatenación con +. size() da la longitud.

List comprehension
// Transformar listas:
RETURN [x IN range(1, 5) | x * 2]
// [2, 4, 6, 8, 10]

// Filtrar:
RETURN [x IN range(1, 10)
        WHERE x % 2 = 0]
// [2, 4, 6, 8, 10]

// Filtrar + transformar:
RETURN [x IN range(1, 10)
        WHERE x > 5 | x * x]
// [36, 49, 64, 81, 100]

// Con nodos:
MATCH (p:Persona)-[:AMIGA_DE]->(a)
RETURN p.nombre,
       [x IN collect(a) | x.edad]

List comprehension: [x IN lista WHERE filtro | expresión] filtra y transforma listas en una sola expresión — igual que en Python. Ideal para extraer valores de colecciones de nodos.

CALL y YIELD
// Procedimientos del sistema:
CALL db.labels()          // labels
CALL db.relationshipTypes() // tipos
CALL dbms.components()    // versión

// Sintaxis:
CALL procedimiento(args)
YIELD columna1, columna2
RETURN columna1

// YIELD extrae las columnas
// del resultado del procedimiento

// Filtrar resultados:
CALL db.labels()
YIELD label
WHERE label STARTS WITH "P"
RETURN label

CALL procedimiento() YIELD columnas invoca procedimientos del sistema o APOC. YIELD extrae columnas del resultado para usar en el resto de la query. db.labels() y db.relationshipTypes() son esenciales para explorar el schema.

Funciones de fecha
// Fecha/hora actual:
RETURN date()           // 2026-07-24
RETURN datetime()       // con hora
RETURN time()           // solo hora
RETURN timestamp()      // epoch ms

// Componentes:
WITH date("2026-07-24") AS d
RETURN d.year, d.month, d.day

// Aritmética de fechas:
RETURN date() + duration("P30D")
// (dentro de 30 días)

RETURN duration({days: 7, hours: 3})

// Diferencia:
RETURN date() - date("2026-01-01")

Tipos temporales: date(), datetime(), time(), timestamp() (epoch). Accede a componentes (.year, .month) y suma/resta con duration() (ej.: P30D = 30 días).

CASE
// CASE simple:
MATCH (p:Persona)
RETURN p.nombre,
  CASE p.ciudad
    WHEN "Porto" THEN "Norte"
    WHEN "Lisbon" THEN "Sur"
    ELSE "Otra"
  END AS region

// CASE con condiciones:
RETURN p.nombre,
  CASE
    WHEN p.edad < 18 THEN "Menor"
    WHEN p.edad < 65 THEN "Adulto"
    ELSE "Sénior"
  END AS escalon

CASE hace lógica condicional en el RETURN: forma simple (CASE prop WHEN valor) o con condiciones (CASE WHEN cond). Termina siempre con END. Equivale al CASE de SQL.

Funciones matemáticas
RETURN abs(-5)        // 5
RETURN round(3.7)     // 4.0
RETURN floor(3.7)     // 3.0
RETURN ceil(3.2)      // 4.0
RETURN sqrt(16)       // 4.0
RETURN rand()         // 0..1

// Redondear con precisión:
RETURN round(3.14159, 2)  // 3.14

// Conversiones:
RETURN toInteger("42")     // 42
RETURN toFloat("3.14")     // 3.14
RETURN toString(42)        // "42"

Matemática: abs, round (con precisión opcional), floor/ceil, sqrt, rand(). Conversiones: toInteger(), toFloat(), toString() — esenciales al importar datos.

APOC (instalación)
// APOC = biblioteca de
// procedimientos esenciales

// Desktop: Database > Plugins
//   > APOC > Install

// Docker:
// -e NEO4J_PLUGINS='["apoc"]'

// Verificar:
CALL apoc.help("")

// Ejemplos de lo que permite:
//   apoc.create.node()
//   apoc.export.json.all()
//   apoc.date.format()
//   apoc.coll.* (listas)

APOC (Awesome Procedures on Cypher) es la biblioteca de procedimientos más usada — cientos de funciones extra. Instala vía Plugins (Desktop) o variable de entorno en Docker. Verifica con CALL apoc.help("").

Funciones de lista
// Tamaño y acceso:
WITH [1, 2, 3] AS l
RETURN size(l), l[0], l[-1]

// Fragmentar:
RETURN l[1..3]       // [2, 3]

// Operaciones:
RETURN [1,2] + [3]   // [1, 2, 3]
RETURN head([1,2,3]) // 1
RETURN tail([1,2,3]) // [2, 3]
RETURN last([1,2,3]) // 3

// Verificar:
RETURN 2 IN [1, 2, 3]  // true

// Range:
RETURN range(1, 5)     // [1,2,3,4,5]

Listas: size(), índices (l[0], l[-1]), fragmentos (l[1..3]), head/tail/last, IN para pertenencia y range() para generar secuencias. Concatenación con +.

APOC (procedimientos útiles)
// Crear nodo con label dinámico:
CALL apoc.create.node(
  ["Persona"], {nombre: "Ana"}
) YIELD node

// Relación dinámica:
CALL apoc.create.relationship(
  a, "AMIGA_DE", {}, b
) YIELD rel

// Exportar BD a JSON:
CALL apoc.export.json.all(
  "backup.json", {}
)

// Convertir epoch:
RETURN apoc.date.format(
  1753372800, "s", "yyyy-MM-dd"
)

APOC brilla en operaciones dinámicas: labels/tipos de relación en variables (apoc.create.node), export a JSON/CSV y conversiones de fechas. Los procedimientos usan CALL ... YIELD.

Cypher Avançado


9 cards
WITH (pipelining)
// WITH pasa resultados a
// la cláusula siguiente:
MATCH (p:Persona)-[:COMPRO]->(pr)
WITH p, count(pr) AS total
WHERE total > 5
RETURN p.nombre, total
ORDER BY total DESC

// ¡Sin WITH no se pueden filtrar
// agregaciones con WHERE!

// WITH también limita el scope:
// solo las variables pasadas
// siguen disponibles

WITH es el pipe de Cypher: pasa resultados (y agregaciones) a la cláusula siguiente. Permite filtrar agregaciones (WHERE tras count) y controlar el scope — solo las variables en el WITH siguen disponibles.

UNION
// Juntar resultados:
MATCH (p:Persona) RETURN p.nombre AS nombre
UNION
MATCH (e:Empresa) RETURN e.nombre AS nombre

// UNION quita duplicados
// UNION ALL los mantiene todos:
MATCH (p:Persona) RETURN p.nombre AS nombre
UNION ALL
MATCH (e:Empresa) RETURN e.nombre AS nombre

// Las columnas tienen que tener
// los MISMOS nombres en ambos

UNION combina resultados de queries con las mismas columnas (quita duplicados); UNION ALL los mantiene todos. Útil para consultar varios labels con la misma estructura de output.

Modelado (buenas prácticas)
// 1. Nodos = entidades (Persona, Filme)
// 2. Relaciones = verbos (ACTUO_EN)
// 3. Props en nodos Y relaciones
// 4. Labels en CamelCase
// 5. Tipos en MAYÚSCULAS
// 6. Dirección = sentido natural
//    (Persona)-[:COMPRO]->(Producto)

// Anti-patrón: listas gigantes
// como propiedad
// MAL:  p.amigos = [1,2,...1M]
// BIEN: (p)-[:AMIGO]->(a)

// ¡Relaciones > arrays de ids!

Buenas prácticas de modelado: nodos = entidades, relaciones = verbos con dirección natural, labels CamelCase, tipos MAYÚSCULAS. Nunca uses arrays gigantes como propiedades — las relaciones son la estructura nativa del grafo.

UNWIND
// Lista → filas:
UNWIND [1, 2, 3] AS x
RETURN x   // 3 filas

// Con MATCH (cruce):
UNWIND ["Ana", "Ray"] AS nombre
MATCH (p:Persona {nombre: nombre})
RETURN p

// Lista de maps → nodos:
UNWIND [{n: "Ana"}, {n: "Ray"}] AS d
MERGE (:Persona {nombre: d.n})

// Revertir collect:
MATCH (p:Persona)
WITH collect(p) AS todos
UNWIND todos AS p
RETURN p.nombre

UNWIND expande listas en filas (inverso de collect()). Combina con MATCH para buscar varios valores y con MERGE para crear nodos a partir de listas de maps. Base de los imports.

LOAD CSV (import)
// Importar CSV (fichero local
// en import/ o URL):
LOAD CSV WITH HEADERS
FROM "file:///personas.csv" AS fila
MERGE (p:Persona {email: fila.email})
SET p.nombre = fila.nombre,
    p.edad = toInteger(fila.edad)

// Con delimitador custom:
LOAD CSV WITH HEADERS
FROM "file:///datos.csv" AS l
FIELDTERMINATOR ";"

// CSV remoto:
FROM "https://ejemplo.com/datos.csv"

LOAD CSV WITH HEADERS importa CSVs fila a fila (cada fila = un map). Combina con MERGE para imports idempotentes y toInteger() para convertir tipos. Soporta URLs y delimitador custom con FIELDTERMINATOR.

Parámetros ($param)
// En lugar de hardcode:
// MATCH (p {nombre: "Ana"})

// Usa parámetro:
MATCH (p:Persona {nombre: $nombre})
RETURN p

// En el Browser, define antes:
:param nombre => "Ana"

// Varios:
:param params => {
  nombre: "Ana", edad: 30
}

// En drivers: pasa en execute()
// ¡Previene inyección de Cypher!

Los parámetros ($nombre) separan datos del código — previenen inyección de Cypher y permiten reutilizar queries. En el Browser define con :param nombre => "Ana"; en drivers, pásalos en el método de ejecución.

Import completo (ejemplo)
// 1. Nodos (con constraint primero):
CREATE CONSTRAINT FOR (p:Persona)
REQUIRE p.email IS UNIQUE

LOAD CSV WITH HEADERS
FROM "file:///personas.csv" AS l
MERGE (p:Persona {email: l.email})
SET p.nombre = l.nombre

// 2. Relaciones:
LOAD CSV WITH HEADERS
FROM "file:///amistades.csv" AS l
MATCH (a:Persona {email: l.de})
MATCH (b:Persona {email: l.para})
MERGE (a)-[:AMIGA_DE]->(b)

// Orden: constraints → nodos
// → relaciones

Import real en 3 pasos: crea constraints primero (unicidad), después nodos con MERGE, por fin relaciones casando los nodos por clave. Este orden garantiza integridad y rendimiento.

Subqueries (CALL {})
// Subquery correlacionada:
MATCH (p:Persona)
CALL {
  WITH p
  MATCH (p)-[:AMIGA_DE]->(a)
  RETURN count(a) AS n_amigos
}
WHERE n_amigos > 3
RETURN p.nombre, n_amigos

// EXISTS subquery:
MATCH (p:Persona)
WHERE EXISTS {
  MATCH (p)-[:TIENE]->(:Coche)
}
RETURN p.nombre

CALL { ... } (Neo4j 4.1+) ejecuta subqueries correlacionadas — usa WITH p para importar variables del exterior. Permite agregaciones por fila y filtros complejos imposibles con un MATCH simple.

Export y backup
// Dump completo (CLI):
neo4j-admin database dump neo4j
  --to-path=/backups

// Restore:
neo4j-admin database load neo4j
  --from-path=/backups

// Export vía APOC:
CALL apoc.export.cypher.all(
  "backup.cypher", {}
)
// genera script Cypher re-ejecutable

// Export solo resultados:
CALL apoc.export.csv.query(
  "MATCH (p:Persona) RETURN p",
  "personas.csv", {}
)

Backup completo con neo4j-admin database dump/load. APOC export genera scripts Cypher re-ejecutables o CSV de queries específicas. Haz dumps regulares antes de cambios grandes.