Cheatsheet Neo4j
Base de dados de grafos com linguagem de consulta Cypher
Neo4j
Instalação e Setup
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.nombreFlujo 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
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/etiquetaCREATE () 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 vezUNWIND 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:AdminLos 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ónUsa 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 actualLas 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
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 filasNeo4j 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 tipoUn 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 pElimina 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)
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 existentesMATCH 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 patronesLos 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.ciudadRETURN 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
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 sincronizacionesEl 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 → deshaceLas 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 dadasDiferencia 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.comentarioLas 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 nDELETE 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
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 NULLREQUIRE 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 kmLos í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
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 escalonCASE 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
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.nombreUNWIND 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
// → relacionesImport 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.nombreCALL { ... } (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.