DevTools

Cheatsheet Neo4j

Base de dados de grafos com linguagem de consulta Cypher

Voltar às linguagens
Neo4j
72 cards encontrados
Categorias:
Versões:

Instalação e Setup


9 cards
Neo4j Desktop
# Windows/macOS/Linux:
# 1. Download em neo4j.com/download
# 2. Instalar Neo4j Desktop (grátis)
# 3. Criar um "Project"
# 4. "Add Database" > Create Local
#    Database (definir password)
# 5. Botão "Start"

# O Desktop inclui:
#   - Servidor Neo4j
#   - Neo4j Browser (interface)
#   - Bloom (visualização)

# Parar: botão "Stop"

O Neo4j Desktop é a forma mais simples (grátis para uso local). Cria um Project, adiciona uma Local Database com password e clica Start. Inclui o Neo4j Browser para consultas visuais.

Primeira query
// Criar um nó:
CREATE (p:Pessoa {nome: "Ana", idade: 30})

// Ver todos os nós:
MATCH (n) RETURN n

// Criar relação:
CREATE (a:Pessoa {nome: "Rui"})
MATCH (x:Pessoa {nome: "Ana"}),
      (y:Pessoa {nome: "Rui"})
CREATE (x)-[:AMIGA_DE]->(y)

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

Fluxo básico: CREATE cria nós, MATCH pesquisa padrões e RETURN devolve resultados. A sintaxe (nós)-[relação]->(nó) é o coração do Cypher — desenha o padrão que procuras.

Neo4j vs SQL
// SQL (tabelas + JOINs):
// SELECT p.nome, a.titulo
// FROM pessoas p
// JOIN amigos f ON f.pessoa_id = p.id
// JOIN pessoas a ON a.id = f.amigo_id
// (lento com muitos JOINs)

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

// Grafos: relações são "ponteiro
// físico" — percorrer é O(1)
// por salto, sem JOINs nem índices

Em SQL, relações vivem em tabelas de JOIN (custo cresce com a profundidade). No Neo4j, relações são ligações nativas — percorrer N saltos é rápido independentemente do tamanho total. Ideal para redes sociais, recomendações e grafos de conhecimento.

Neo4j Aura (cloud)
# Serviço gerido na cloud:
#   console.neo4j.io

# Aura Free (grátis para sempre):
#   200k nós + 400k relações
#   1 instância

# Passos:
# 1. Criar conta
# 2. "Create Instance"
# 3. Escolher região e password
# 4. Guardar as credenciais
#    (mostradas só uma vez!)

# Ligação: neo4j+s://xxxx.databases.neo4j.io

Neo4j Aura é a versão cloud gerida — o plano Free serve para aprender (200k nós). Cria a instância em console.neo4j.io e guarda as credenciais (mostradas só uma vez). Liga via neo4j+s://.

Dataset de exemplo (movies)
// No Browser:
:play movies

// Carrega um grafo de filmes com:
//   Person, Movie (nós)
//   ACTED_IN, DIRECTED (relações)

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

// Quem realizou "The Matrix"?
MATCH (d:Person)-[:DIRECTED]->
      (m:Movie {title: "The Matrix"})
RETURN d.name

:play movies carrega um grafo de exemplo (atores, filmes, realizadores) perfeito para aprender. Usa MATCH com padrões como (p:Person)-[:ACTED_IN]->(m:Movie) para explorar relações reais.

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

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

# -v persiste os dados
# NEO4J_AUTH define user/password
# (sem isto, pede setup no browser)

Em Docker: expõe as portas 7474 (browser) e 7687 (Bolt para drivers). Define NEO4J_AUTH para user/password e monta um volume em /data para persistir. Ideal para desenvolvimento.

Configuração (neo4j.conf)
# Ficheiro: conf/neo4j.conf
# (no Desktop: Database > "..."
#  > Settings)

# Memória (ajustar ao hardware):
server.memory.heap.initial_size=1G
server.memory.heap.max_size=2G
server.memory.pagecache.size=1G

# Permitir ligações remotas:
server.default_listen_address=0.0.0.0

# Reiniciar após alterações:
# Database > Stop > Start

Configurações em conf/neo4j.conf: heap e pagecache controlam memória (pagecache ≈ tamanho dos dados). default_listen_address=0.0.0.0 permite ligações remotas. Reinicia o serviço após alterações.

Neo4j Browser
# Abrir: http://localhost:7474
# (ou botão "Open" no Desktop)

# Login:
#   Connect URL: bolt://localhost:7687
#   Username: neo4j
#   Password: (a tua)

# Interface:
#   :help          → ajuda
#   :clear         → limpar ecrã
#   :play movies   → dataset exemplo
#   Ctrl+Enter     → executar query

# Resultados em grafo ou tabela
# (botões Graph | Table | Text)

O Neo4j Browser (porta 7474) é a interface de consultas. Comandos úteis: :help, :clear, :play movies (dataset de exemplo). Executa com Ctrl+Enter e vê resultados como grafo ou tabela.

Utilizadores e roles
// Mudar password:
ALTER CURRENT USER
SET PASSWORD FROM "atual" TO "nova"

// Criar utilizador:
CREATE USER maria
SET PASSWORD "senha123"
SET PASSWORD CHANGE NOT REQUIRED

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

// Ver utilizadores:
SHOW USERS

// Remover:
DROP USER maria

Gere utilizadores com CREATE USER e roles (reader, editor, admin) via GRANT ROLE. SHOW USERS lista todos. Muda a tua password com ALTER CURRENT USER.

Criar Nós


9 cards
CREATE (nó simples)
// Nó sem label nem propriedades:
CREATE ()

// Nó com label:
CREATE (:Pessoa)

// Nó com propriedades:
CREATE (:Pessoa {
  nome: "Ana",
  idade: 30,
  ativa: true
})

// Parêntesis = nó
// {} = propriedades (map)
// :Label = tipo/etiqueta

CREATE () cria um nó. :Label dá-lhe um tipo (ex.: Pessoa) e {} define propriedades (map chave/valor). Labels permitem filtrar em consultas — usa sempre.

Criar múltiplos nós
// Vários nós numa query:
CREATE (:Pais {nome: "Portugal"}),
       (:Pais {nome: "Espanha"}),
       (:Pais {nome: "França"})

// Nós já ligados:
CREATE (a:Pessoa {nome: "Ana"})
       -[:SEGUE]->
       (b:Pessoa {nome: "Rui"})

// Contar nós criados:
CREATE (n:Teste)
RETURN count(n)

Cria vários nós separados por vírgulas num só CREATE. Podes criar nós já ligados com a sintaxe de padrão completa — mais eficiente que criar e ligar em queries separadas.

UNWIND para bulk create
// Criar muitos nós de uma lista:
UNWIND ["Ana", "Rui", "Maria"] AS nome
CREATE (:Pessoa {nome: nome})

// Com maps (várias propriedades):
UNWIND [
  {nome: "Ana", idade: 30},
  {nome: "Rui", idade: 25}
] AS dados
CREATE (p:Pessoa)
SET p = dados

// SET p = map define todas
// as propriedades de uma vez

UNWIND transforma listas em linhas — perfeito para criar muitos nós numa query. Com maps, SET p = dados aplica todas as propriedades de uma vez. Muito mais rápido que CREATEs separados.

Labels
// Um nó pode ter VÁRIOS labels:
CREATE (n:Funcionario:Gerente
        {nome: "Rui"})

// Labels em CamelCase
// (convenção: Pessoa, ContaBancaria)

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

// Remover label:
MATCH (n:Gerente {nome: "Rui"})
REMOVE n:Gerente

// Adicionar label:
MATCH (n {nome: "Rui"})
SET n:Admin

Labels são etiquetas — um nó pode ter vários (ex.: :Funcionario:Gerente). Convenção: CamelCase. CALL db.labels() lista todos. Adiciona com SET n:Label, remove com REMOVE n:Label.

MERGE (criar ou encontrar)
// CREATE duplica se já existir:
CREATE (:Pessoa {nome: "Ana"})
CREATE (:Pessoa {nome: "Ana"})  // 2 Anas!

// MERGE = "garante que existe":
MERGE (p:Pessoa {nome: "Ana"})
ON CREATE SET p.criado = timestamp()
ON MATCH SET p.visto = timestamp()
RETURN p

// Se não existir → cria
// Se existir → usa o existente

// MERGE procura TUDO o padrão
// (label + propriedades dadas)

MERGE é o CREATE idempotente: procura o padrão e só cria se não existir — evita duplicados. ON CREATE SET corre na criação, ON MATCH SET quando já existe. Essencial para imports.

Propriedades
// Tipos suportados:
CREATE (:Produto {
  nome: "Portátil",       // String
  preco: 999.99,          // Double
  stock: 5,               // Integer
  disponivel: true,       // Boolean
  tags: ["tech", "novo"], // Lista
  lancamento: date()      // Temporal
})

// Propriedades NÃO podem ser:
//   maps aninhados, null
//   (null = propriedade ausente)

// Ver propriedades de um nó:
MATCH (p:Produto) RETURN properties(p)

Propriedades aceitam String, números, Boolean, listas e tipos temporais (date(), datetime()). Não aceitam maps aninhados nem null (null = propriedade inexistente). properties(n) mostra todas.

MERGE com relações
// Garantir pessoa E relação:
MERGE (a:Pessoa {nome: "Ana"})
MERGE (b:Pessoa {nome: "Rui"})
MERGE (a)-[:AMIGA_DE]->(b)

// Erro comum: MERGE no padrão
// completo sem os nós existirem
// → cria tudo de uma vez (ok),
// mas lento em grafos grandes

// Melhor: MERGE nós primeiro,
// depois MERGE a relação

Usa MERGE separado para cada nó e depois para a relação — garante que nada é duplicado. Fazer MERGE do padrão completo funciona mas é mais lento e menos explícito.

Variáveis de nó
// Atribuir o nó a uma variável:
CREATE (p:Pessoa {nome: "Ana"})
RETURN p

// RETURN devolve o nó criado
// (com id interno e propriedades)

// Usar em multi-statement:
CREATE (a:Pessoa {nome: "Rui"})
CREATE (b:Cidade {nome: "Lisboa"})
CREATE (a)-[:VIVE_EM]->(b)
RETURN a, b

// Variáveis só existem
// dentro da query atual

Variáveis (p, a, b) referenciam nós criados para usar no RETURN ou em relações na mesma query. Não persistem entre queries — para reutilizar, pesquisa com MATCH.

ID interno e elementId
// Cada nó tem um id interno:
MATCH (p:Pessoa)
RETURN id(p), p.nome

// Neo4j 5+: elementId (String):
MATCH (p:Pessoa)
RETURN elementId(p), p.nome

// ATENÇÃO: ids são REUTILIZADOS
// após DELETE — nunca usar como
// chave externa permanente!

// Chave estável: cria a tua
// propriedade (ex.: uuid, email)

id() devolve o id interno (inteiro) e elementId() (Neo4j 5+) uma string. Ambos são reutilizados após DELETE — nunca servem como chave permanente. Cria a tua propriedade única (uuid, email).

Relações


9 cards
CREATE (relação)
// Ligar dois nós existentes:
MATCH (a:Pessoa {nome: "Ana"}),
      (b:Pessoa {nome: "Rui"})
CREATE (a)-[:AMIGA_DE]->(b)

// Sintaxe:
//   (origem)-[:TIPO]->(destino)
//   -[:TIPO]->  = direção →
//   <-[:TIPO]-  = direção ←
//   -[:TIPO]-   = sem direção

// TIPO em MAIÚSCULAS_COM_UNDERSCORE
// (convenção: AMIGA_DE, TRABALHA_EM)

Relações criam-se com (a)-[:TIPO]->(b) — a seta define a direção. Tipos seguem a convenção MAIÚSCULAS_COM_UNDERSCORE. Faz MATCH dos nós primeiro e depois CREATE da relação.

Padrões de relação
// Filtrar por tipo:
MATCH (a)-[:AMIGA_DE]->(b)
RETURN a, b

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

// Com label no destino:
MATCH (p:Pessoa)-[:COMPROU]->
      (pr:Produto)
RETURN p.nome, pr.nome

// Relação sem tipo (qualquer):
MATCH (a)-[r]->(b)
RETURN type(r), count(*)

Padrões flexíveis: [:TIPO1|TIPO2] casa vários tipos (OR), labels no destino filtram nós, e [r] sem tipo casa qualquer relação. Combina com count(*) para analisar a distribuição de tipos.

Relações self-loop e multi
// Self-loop (nó ligado a si):
MATCH (p:Pessoa {nome: "Ana"})
CREATE (p)-[:MENTOR_DE]->(p)

// Múltiplas relações do mesmo tipo
// entre os mesmos nós (permitido):
MATCH (a {nome: "Ana"}), (b {nome: "Rui"})
CREATE (a)-[:PAGOU {valor: 10}]->(b)
CREATE (a)-[:PAGOU {valor: 20}]->(b)

// Consultar ambas:
MATCH (a {nome: "Ana"})-[r:PAGOU]->(b)
RETURN r.valor   // 2 linhas

O Neo4j permite self-loops (nó ligado a si próprio) e múltiplas relações do mesmo tipo entre o mesmo par de nós (ex.: vários pagamentos). Cada relação é uma entidade independente com propriedades próprias.

Propriedades em relações
// Relações também têm propriedades:
MATCH (a:Pessoa {nome: "Ana"}),
      (m:Movie {title: "Matrix"})
CREATE (a)-[:AVALIOU {
  nota: 5,
  data: date(),
  comentario: "Excelente!"
}]->(m)

// Ler propriedades:
MATCH (a)-[r:AVALIOU]->(m)
RETURN a.nome, r.nota, m.title

// Variável da relação: [r:TIPO]

Relações aceitam propriedades como nós — ideal para metadados (nota, data). Captura a relação numa variável com [r:TIPO] para aceder às propriedades no RETURN.

Caminhos variáveis
// Amigos de amigos (2 saltos):
MATCH (a:Pessoa {nome: "Ana"})
      -[:AMIGA_DE]->()-[:AMIGA_DE]->(b)
RETURN b.nome

// Com comprimento variável:
MATCH (a {nome: "Ana"})
      -[:AMIGA_DE*1..3]->(b)
RETURN DISTINCT b.nome

// *1..3 = 1 a 3 saltos
// *2    = exatamente 2
// *..5  = até 5
// *     = qualquer (cuidado!)

// DISTINCT evita duplicados

-[:TIPO*1..3]-> percorre caminhos de comprimento variável (1 a 3 saltos) — a grande vantagem dos grafos. Usa DISTINCT para evitar duplicados e limita sempre o máximo (caminhos sem limite podem explodir).

Direção
// Criar com direção:
CREATE (a)-[:SEGUE]->(b)    // a segue b

// Consultar com direção:
MATCH (a:Pessoa)-[:SEGUE]->(b)
RETURN a.nome, b.nome

// Consultar SEM direção
// (encontra ambos os sentidos):
MATCH (a:Pessoa)-[:SEGUE]-(b)
RETURN a.nome, b.nome

// A direção é SEMPRE guardada
// (não existe relação "sem direção"
//  — só a ignoras na query)

A direção é sempre armazenada-[]-> cria/consulta num sentido, -[]- ignora a direção na consulta (encontra ambos). Escolhe a direção ao modelar (ex.: SEGUE tem direção natural).

shortestPath
// Caminho mais curto entre 2 nós:
MATCH (a:Pessoa {nome: "Ana"}),
      (z:Pessoa {nome: "Zé"}),
      caminho = shortestPath(
        (a)-[:AMIGA_DE*..10]-(z)
      )
RETURN caminho

// Nós do caminho:
RETURN nodes(caminho)

// Relações do caminho:
RETURN relationships(caminho)

// Comprimento:
RETURN length(caminho)

shortestPath() encontra o caminho mínimo entre dois nós (algoritmo BFS nativo). Extrai detalhes com nodes(), relationships() e length(). Limita com *..10 para performance.

Vários tipos de relação
// Criar relações diferentes:
MATCH (a:Pessoa {nome: "Ana"}),
      (e:Empresa {nome: "TechCorp"})
CREATE (a)-[:TRABALHA_EM {desde: 2020}]->(e)

MATCH (a:Pessoa {nome: "Ana"}),
      (c:Cidade {nome: "Porto"})
CREATE (a)-[:VIVE_EM]->(c)

// Consultar qualquer uma:
MATCH (a:Pessoa {nome: "Ana"})-[r]->()
RETURN type(r)

// type(r) devolve o nome do tipo

Um nó pode ter relações de vários tipos (TRABALHA_EM, VIVE_EM...). [r]->() casa qualquer relação a sair; type(r) devolve o tipo como string. () sem label casa qualquer nó.

Apagar relações
// Apagar uma relação específica:
MATCH (a:Pessoa {nome: "Ana"})
      -[r:AMIGA_DE]->(b {nome: "Rui"})
DELETE r

// Apagar TODAS as relações de um nó:
MATCH (p:Pessoa {nome: "Ana"})-[r]-()
DELETE r

// O nó continua a existir
// (só as relações são apagadas)

// Apagar nó E relações:
MATCH (p:Pessoa {nome: "Ana"})
DETACH DELETE p

Apaga relações com DELETE r (o nó mantém-se). MATCH (p)-[r]-() + DELETE r remove todas as ligações de um nó. Para apagar um nó com relações usa DETACH DELETE (senão dá erro).

Consultas (MATCH)


9 cards
MATCH básico
// Todos os nós:
MATCH (n) RETURN n

// Todos os nós com label:
MATCH (p:Pessoa) RETURN p

// Com propriedades:
MATCH (p:Pessoa {nome: "Ana"})
RETURN p

// Várias condições:
MATCH (p:Pessoa {nome: "Ana", idade: 30})
RETURN p

// MATCH não cria nada —
// só pesquisa padrões existentes

MATCH pesquisa padrões no grafo (nunca cria). Filtra por label (:Pessoa) e propriedades ({nome: "Ana"}). Sem padrões, devolve todos os nós — limita sempre com WHERE/LIMIT.

OPTIONAL MATCH
// LEFT JOIN do Cypher:
MATCH (p:Pessoa)
OPTIONAL MATCH (p)-[:TEM]->(c:Carro)
RETURN p.nome, c.modelo

// Pessoas SEM carro aparecem
// com c.modelo = null

// Comparar:
// MATCH normal → só pessoas
//   com carro (inner join)
// OPTIONAL MATCH → todas as
//   pessoas (left join)

OPTIONAL MATCH é o LEFT JOIN do Cypher: devolve resultados mesmo quando o padrão não casa (com null nas variáveis não casadas). O MATCH normal equivale a INNER JOIN.

collect (listas)
// Juntar valores numa lista:
MATCH (p:Pessoa)
RETURN collect(p.nome)

// Amigos de cada pessoa:
MATCH (p:Pessoa)-[:AMIGA_DE]->(a)
RETURN p.nome, collect(a.nome) AS amigos

// Tamanho da lista:
RETURN p.nome,
       size(collect(a.nome)) AS n_amigos

// collect() nunca devolve null
// (lista vazia se nada casar)

collect() agrega valores numa lista — perfeito para "todos os amigos de cada pessoa". size() dá o comprimento. Nunca devolve null (lista vazia sim), ao contrário de outras agregações.

WHERE
MATCH (p:Pessoa)
WHERE p.idade > 25
  AND p.ativa = true
RETURN p.nome

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

// Existência de propriedade:
WHERE p.email IS NOT NULL

// IN:
WHERE p.cidade IN ["Porto", "Lisboa"]

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

WHERE filtra com operadores (>, <>), AND/OR/NOT, IN para listas e IS NOT NULL para existência. Strings: STARTS WITH, CONTAINS, ENDS WITH (e regex com =~).

Padrões complexos
// Triângulo de amizade:
MATCH (a:Pessoa)-[:AMIGA_DE]->(b),
      (b)-[:AMIGA_DE]->(c),
      (c)-[:AMIGA_DE]->(a)
RETURN a.nome, b.nome, c.nome

// Pessoa que comprou o mesmo
// produto que outra:
MATCH (p1)-[:COMPROU]->(pr)<-[:COMPROU]-(p2)
WHERE p1 <> p2
RETURN p1.nome, p2.nome, pr.nome

// Reutilizar variáveis cria
// a ligação entre padrões

Padrões complexos ligam-se reutilizando variáveis (b aparece em duas relações; pr é partilhado). WHERE p1 <> p2 exclui o próprio. É assim que se expressam triângulos, recomendações e caminhos.

RETURN
// Nós completos:
MATCH (p:Pessoa) RETURN p

// Propriedades específicas:
RETURN p.nome, p.idade

// Alias:
RETURN p.nome AS nome,
       p.idade AS "Anos de idade"

// Expressões:
RETURN p.nome, p.idade * 2

// Tudo:
MATCH (p:Pessoa) RETURN *

// Distintos:
RETURN DISTINCT p.cidade

RETURN define o output: nós completos, propriedades, aliases (AS) ou expressões. RETURN * devolve todas as variáveis. DISTINCT remove duplicados dos resultados.

WHERE com relações
// Filtrar por EXISTÊNCIA de padrão:
MATCH (p:Pessoa)
WHERE EXISTS {
  (p)-[:TEM]->(:Carro)
}
RETURN p.nome

// Ou negação (sem carro):
MATCH (p:Pessoa)
WHERE NOT EXISTS {
  (p)-[:TEM]->(:Carro)
}
RETURN p.nome

// EXISTS {} = subquery de
// existência (Neo4j 4.4+)

EXISTS { padrão } filtra nós pela existência de um padrão (sem o devolver). NOT EXISTS faz a negação — "pessoas sem carro". Substitui subqueries de existência do SQL.

ORDER BY, SKIP, LIMIT
// Ordenar:
MATCH (p:Pessoa)
RETURN p.nome, p.idade
ORDER BY p.idade DESC

// Paginação:
MATCH (p:Pessoa)
RETURN p.nome
ORDER BY p.nome
SKIP 10        // saltar 10
LIMIT 5        // trazer 5

// Top 3 mais velhos:
MATCH (p:Pessoa)
RETURN p.nome, p.idade
ORDER BY p.idade DESC
LIMIT 3

ORDER BY ordena (DESC para descendente). SKIP + LIMIT fazem paginação. Combina os três para top-N: ORDER BY ... DESC LIMIT 3.

Contar e agregar
// Contar nós:
MATCH (p:Pessoa) RETURN count(p)

// Contar relações:
MATCH ()-[r:AMIGA_DE]->()
RETURN count(r)

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

// Média, soma, min, max:
MATCH (p:Pessoa)
RETURN avg(p.idade),
       min(p.idade),
       max(p.idade),
       sum(p.idade)

Agregação é implícita: count(), avg(), sum(), min(), max(). O agrupamento faz-se pelas colunas não agregadas (como GROUP BY automático). count(r) conta relações.

Atualizar e Apagar


9 cards
SET (atualizar)
// Atualizar uma propriedade:
MATCH (p:Pessoa {nome: "Ana"})
SET p.idade = 31
RETURN p

// Várias propriedades:
MATCH (p:Pessoa {nome: "Ana"})
SET p.idade = 31,
    p.cidade = "Porto",
    p.atualizado = timestamp()

// Adicionar propriedade nova:
MATCH (p:Pessoa {nome: "Ana"})
SET p.email = "ana@mail.com"

// SET só atualiza nós existentes
// (MATCH tem de os encontrar)

SET atualiza propriedades em nós encontrados via MATCH. Serve para alterar valores, adicionar propriedades novas e múltiplos campos de uma vez. Não cria nada — o nó tem de existir.

MERGE ON CREATE / ON MATCH
// Upsert do Cypher:
MERGE (p:Pessoa {email: "ana@mail.com"})
ON CREATE SET
  p.nome = "Ana",
  p.criado = datetime(),
  p.novo = true
ON MATCH SET
  p.ultimo_login = datetime()
RETURN p

// Primeira vez: cria com tudo
// Seguintes: só atualiza
//   ultimo_login

// Padrão essencial para imports
// e sincronizações

O upsert do Neo4j: MERGE + ON CREATE SET (só na criação) + ON MATCH SET (só se já existir). Perfeito para imports idempotentes — corre a mesma query várias vezes sem duplicar.

Transações explícitas
// No Browser, tudo é transação.
// Em drivers (ex.: Python):

// BEGIN
//   CREATE (:Pessoa {nome: "Ana"})
// COMMIT

// Ou rollback:
// BEGIN
//   CREATE (:Pessoa {nome: "Erro"})
// ROLLBACK   // desfaz

// No Browser:
// :begin  → inicia transação
// :commit → confirma
// :rollback → desfaz

Transações garantem atomicidade: BEGIN + COMMIT confirma, ROLLBACK desfaz. No Browser usa :begin/:commit. Em drivers, usa as APIs transacionais (nunca deixes transações abertas).

SET com map (+= e =)
// = substitui TODAS as props:
MATCH (p:Pessoa {nome: "Ana"})
SET p = {nome: "Ana", idade: 31}
// (email, cidade... apagados!)

// += faz MERGE das props:
MATCH (p:Pessoa {nome: "Ana"})
SET p += {idade: 31, cidade: "Porto"}
// (outras props mantidas)

// Regra:
//   SET p = map   → substitui tudo
//   SET p += map  → atualiza só
//                    as dadas

Diferença crítica: SET p = map substitui todas as propriedades (as omitidas são apagadas); SET p += map só atualiza as dadas, mantendo as restantes. Usa += para updates parciais.

Atualizar relações
// SET em relação:
MATCH (a:Pessoa {nome: "Ana"})
      -[r:AVALIOU]->(m:Movie)
SET r.nota = 4,
    r.revisto = date()
RETURN r

// Adicionar propriedade:
MATCH ()-[r:AMIGA_DE]->()
SET r.confirmada = true

// Remover propriedade:
MATCH ()-[r:AVALIOU]->()
REMOVE r.comentario

Relações atualizam-se igual a nós: captura com [r:TIPO] e usa SET r.prop = valor ou REMOVE r.prop. As mesmas regras de += e null aplicam-se.

REMOVE
// Remover propriedade:
MATCH (p:Pessoa {nome: "Ana"})
REMOVE p.email
RETURN p

// Remover label:
MATCH (p:Gerente {nome: "Rui"})
REMOVE p:Gerente

// Remover várias:
MATCH (p:Pessoa {nome: "Ana"})
REMOVE p.email, p.telefone

// Alternativa: SET p.email = null
// (efeito igual — remove a prop)

REMOVE apaga propriedades ou labels de um nó. SET p.email = null tem o mesmo efeito (propriedades null não existem no Neo4j). Útil para limpar dados obsoletos.

FOREACH
// Atualizar vários nós de uma lista:
MATCH (p:Pessoa)
WHERE p.cidade = "Lisboa"
FOREACH (x IN collect(p) |
  SET x.regiao = "Grande Lisboa"
)

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

// FOREACH itera listas e
// executa SET/CREATE/MERGE/DELETE

FOREACH (x IN lista | SET ...) itera listas para atualizar múltiplos elementos — útil em nodes(caminho) para marcar todos os nós de um caminho. Suporta SET, CREATE, MERGE e DELETE.

DELETE e DETACH DELETE
// Apagar nó SEM relações:
MATCH (p:Pessoa {nome: "Teste"})
DELETE p

// Erro se o nó tiver relações!
// (Neo4j protege a integridade)

// Apagar nó COM relações:
MATCH (p:Pessoa {nome: "Teste"})
DETACH DELETE p
// (apaga as relações primeiro)

// Apagar TUDO (cuidado!):
MATCH (n) DETACH DELETE n

DELETE apaga nós sem relações — se tiver, dá erro (proteção de integridade). DETACH DELETE apaga o nó e todas as suas relações. MATCH (n) DETACH DELETE n limpa a BD inteira — usa com cuidado.

Apagar em lote (seguro)
// LENTO (transação gigante):
// MATCH (n) DETACH DELETE n

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

// Repetir até devolver 0
// (ou em script: while affected > 0)

// Apagar relações em lote:
MATCH ()-[r:TEMPORARIA]->()
WITH r LIMIT 10000
DELETE r

Apagar milhões de nós de uma vez bloqueia a BD. Faz por lotes: WITH n LIMIT 10000 DETACH DELETE n e repete até afetar 0. Cada lote é uma transação pequena — não esgota memória.

Índices e Constraints


9 cards
CREATE INDEX
// Índice numa propriedade:
CREATE INDEX pessoa_nome
FOR (p:Pessoa) ON (p.nome)

// Composto (várias props):
CREATE INDEX pessoa_nome_idade
FOR (p:Pessoa) ON (p.nome, p.idade)

// Em relações (Neo4j 5+):
CREATE INDEX avaliou_nota
FOR ()-[r:AVALIOU]-() ON (r.nota)

// Índices aceleram MATCH/WHERE
// mas custam em writes

CREATE INDEX ... FOR (n:Label) ON (n.prop) acelera pesquisas por essa propriedade. Suporta índices compostos (várias props) e em relações. Trade-off: writes ficam mais lentos — indexa só o necessário.

SHOW e DROP
// Listar índices:
SHOW INDEXES

// Listar constraints:
SHOW CONSTRAINTS

// Detalhes de um índice:
SHOW INDEXES WHERE name = "pessoa_nome"

// Apagar índice:
DROP INDEX pessoa_nome

// Apagar constraint:
DROP CONSTRAINT pessoa_email_unica

// Nomes aparecem na coluna
// "name" dos resultados

SHOW INDEXES e SHOW CONSTRAINTS listam tudo (com nomes e estado). Remove com DROP INDEX nome / DROP CONSTRAINT nome. Verifica sempre antes de criar duplicados.

Regras de performance
// 1. Indexa props de MATCH/WHERE
// 2. Usa labels (nunca MATCH (n)
//    sem filtro em BDs grandes)
// 3. LIMIT cedo:
MATCH (p:Pessoa)
WHERE p.cidade = "Porto"
RETURN p LIMIT 100

// 4. Padrões seletivos primeiro:
//    (começa pelo nó mais raro)
// 5. Evita * sem limite máximo
// 6. PROFILE nas queries lentas

Regras de ouro: indexa propriedades filtradas, começa padrões pelos nós mais seletivos, usa LIMIT cedo, limita caminhos variáveis e valida com PROFILE. Grafos rápidos = padrões bem desenhados.

Constraint de unicidade
// Garantir email único:
CREATE CONSTRAINT pessoa_email_unica
FOR (p:Pessoa)
REQUIRE p.email IS UNIQUE

// Tentar duplicar → ERRO:
CREATE (:Pessoa {email: "a@b.com"})
CREATE (:Pessoa {email: "a@b.com"})
// ConstraintValidationFailed

// Constraints criam índice
// automaticamente (não duplicar!)

REQUIRE p.email IS UNIQUE impede duplicados — a segunda inserção falha com erro. A constraint cria um índice automático (não cries outro igual). Fundamental para chaves de negócio (email, uuid).

Índices fulltext
// Índice de texto completo:
CREATE FULLTEXT INDEX pesquisa_nome
FOR (p:Pessoa|Empresa)
ON EACH [p.nome, p.descricao]

// Pesquisar (com score):
CALL db.index.fulltext.queryNodes(
  "pesquisa_nome", "ana silva"
)
YIELD node, score
RETURN node.nome, score

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

Índices fulltext permitem pesquisa por texto em várias propriedades/labels com score de relevância. Consulta via db.index.fulltext.queryNodes(). Suporta operadores booleanos e wildcards.

Constraint de existência
// Propriedade obrigatória:
CREATE CONSTRAINT pessoa_nome
FOR (p:Pessoa)
REQUIRE p.nome IS NOT NULL

// Criar sem nome → ERRO:
CREATE (:Pessoa {idade: 30})
// falha (nome em falta)

// Label obrigatório (Neo4j 5+):
CREATE CONSTRAINT tem_label
FOR (p:Pessoa)
REQUIRE p:Ativa IS NOT NULL

REQUIRE p.nome IS NOT NULL torna a propriedade obrigatória — inserções sem ela falham. Garante qualidade de dados na origem (Enterprise para algumas; existence constraints variam por versão).

Índices point e range
// Índice espacial (geo):
CREATE INDEX ponto_local
FOR (l:Local) ON (l.localizacao)

// Criar ponto:
CREATE (l:Local {
  nome: "Sede",
  localizacao: point({
    latitude: 41.15, longitude: -8.61
  })
})

// Consultar por distância:
MATCH (l:Local)
WHERE distance(l.localizacao,
  point({latitude: 41.16,
         longitude: -8.60})) < 5000
RETURN l.nome   // < 5 km

Índices point aceleram queries geoespaciais. Cria coordenadas com point({latitude, longitude}) e mede distâncias com distance() (metros). Útil para "perto de mim".

Node Key
// Chave primária composta:
CREATE CONSTRAINT pessoa_key
FOR (p:Pessoa)
REQUIRE (p.nome, p.data_nasc)
IS NODE KEY

// NODE KEY = UNIQUE + NOT NULL
// em todas as propriedades

// Erro se faltar uma:
CREATE (:Pessoa {nome: "Ana"})
// falha (data_nasc em falta)

// Erro se duplicada:
// (mesmo nome + data_nasc)

NODE KEY é a chave primária do Neo4j: combina unicidade + NOT NULL em várias propriedades. Ideal para chaves compostas de negócio (ex.: nome + data de nascimento).

EXPLAIN e PROFILE
// Ver plano SEM executar:
EXPLAIN MATCH (p:Pessoa)
WHERE p.nome = "Ana" RETURN p

// Ver plano COM métricas reais:
PROFILE MATCH (p:Pessoa)
WHERE p.nome = "Ana" RETURN p

// O que procurar:
//   NodeByLabelScan → lento!
//     (falta índice)
//   NodeIndexSeek → rápido ✅
//   db hits → quanto menor melhor

EXPLAIN mostra o plano de execução sem correr; PROFILE corre e dá métricas reais (db hits, linhas). NodeIndexSeek = usa índice (bom); NodeByLabelScan = varre tudo (cria índice!).

Funções e APOC


9 cards
Funções de string
RETURN upper("ana")        // "ANA"
RETURN lower("ANA")        // "ana"
RETURN trim("  oi  ")      // "oi"
RETURN size("olá")         // 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 "Olá " + "Mundo"

Funções de string: upper/lower/trim, substring, replace, split (devolve lista), left/right. Concatenação com +. size() dá o comprimento.

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]

// Com nós:
MATCH (p:Pessoa)-[:AMIGA_DE]->(a)
RETURN p.nome,
       [x IN collect(a) | x.idade]

List comprehension: [x IN lista WHERE filtro | expressão] filtra e transforma listas numa expressão — como em Python. Ideal para extrair valores de coleções de nós.

CALL e YIELD
// Procedimentos do sistema:
CALL db.labels()          // labels
CALL db.relationshipTypes() // tipos
CALL dbms.components()    // versão

// Sintaxe:
CALL procedimento(args)
YIELD coluna1, coluna2
RETURN coluna1

// YIELD extrai as colunas
// do resultado do procedimento

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

CALL procedimento() YIELD colunas invoca procedimentos do sistema ou APOC. YIELD extrai colunas do resultado para usar no resto da query. db.labels() e db.relationshipTypes() são essenciais para explorar o schema.

Funções de data
// Data/hora atual:
RETURN date()           // 2026-07-24
RETURN datetime()       // com hora
RETURN time()           // só hora
RETURN timestamp()      // epoch ms

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

// Aritmética de datas:
RETURN date() + duration("P30D")
// (daqui a 30 dias)

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

// Diferença:
RETURN date() - date("2026-01-01")

Tipos temporais: date(), datetime(), time(), timestamp() (epoch). Acede a componentes (.year, .month) e soma/subtrai com duration() (ex.: P30D = 30 dias).

CASE
// CASE simples:
MATCH (p:Pessoa)
RETURN p.nome,
  CASE p.cidade
    WHEN "Porto" THEN "Norte"
    WHEN "Lisboa" THEN "Sul"
    ELSE "Outra"
  END AS regiao

// CASE com condições:
RETURN p.nome,
  CASE
    WHEN p.idade < 18 THEN "Menor"
    WHEN p.idade < 65 THEN "Adulto"
    ELSE "Sénior"
  END AS escalao

CASE faz lógica condicional no RETURN: forma simples (CASE prop WHEN valor) ou com condições (CASE WHEN cond). Termina sempre com END. Equivale ao CASE do SQL.

Funções 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

// Arredondar com precisão:
RETURN round(3.14159, 2)  // 3.14

// Conversões:
RETURN toInteger("42")     // 42
RETURN toFloat("3.14")     // 3.14
RETURN toString(42)        // "42"

Matemática: abs, round (com precisão opcional), floor/ceil, sqrt, rand(). Conversões: toInteger(), toFloat(), toString() — essenciais ao importar dados.

APOC (instalação)
// APOC = biblioteca de
// procedimentos essenciais

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

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

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

// Exemplos do que permite:
//   apoc.create.node()
//   apoc.export.json.all()
//   apoc.date.format()
//   apoc.coll.* (listas)

APOC (Awesome Procedures on Cypher) é a biblioteca de procedimentos mais usada — centenas de funções extra. Instala via Plugins (Desktop) ou variável de ambiente no Docker. Verifica com CALL apoc.help("").

Funções de lista
// Tamanho e acesso:
WITH [1, 2, 3] AS l
RETURN size(l), l[0], l[-1]

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

// Operações:
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]), fatias (l[1..3]), head/tail/last, IN para pertença e range() para gerar sequências. Concatenação com +.

APOC (procedimentos úteis)
// Criar nó com label dinâmico:
CALL apoc.create.node(
  ["Pessoa"], {nome: "Ana"}
) YIELD node

// Relação dinâmica:
CALL apoc.create.relationship(
  a, "AMIGA_DE", {}, b
) YIELD rel

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

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

APOC brilha em operações dinâmicas: labels/tipos de relação em variáveis (apoc.create.node), export para JSON/CSV e conversões de datas. Procedimentos usam CALL ... YIELD.

Cypher Avançado


9 cards
WITH (pipelining)
// WITH passa resultados para
// a cláusula seguinte:
MATCH (p:Pessoa)-[:COMPROU]->(pr)
WITH p, count(pr) AS total
WHERE total > 5
RETURN p.nome, total
ORDER BY total DESC

// Sem WITH não dá para filtrar
// agregações com WHERE!

// WITH também limita o scope:
// só as variáveis passadas
// continuam disponíveis

WITH é o pipe do Cypher: passa resultados (e agregações) para a cláusula seguinte. Permite filtrar agregações (WHERE após count) e controlar o scope — só as variáveis no WITH continuam disponíveis.

UNION
// Juntar resultados:
MATCH (p:Pessoa) RETURN p.nome AS nome
UNION
MATCH (e:Empresa) RETURN e.nome AS nome

// UNION remove duplicados
// UNION ALL mantém todos:
MATCH (p:Pessoa) RETURN p.nome AS nome
UNION ALL
MATCH (e:Empresa) RETURN e.nome AS nome

// As colunas têm de ter
// os MESMOS nomes em ambos

UNION combina resultados de queries com as mesmas colunas (remove duplicados); UNION ALL mantém todos. Útil para pesquisar vários labels com a mesma estrutura de output.

Modelação (boas práticas)
// 1. Nós = entidades (Pessoa, Filme)
// 2. Relações = verbos (ATUOU_EM)
// 3. Props em nós E relações
// 4. Labels em CamelCase
// 5. Tipos em MAIÚSCULAS
// 6. Direção = sentido natural
//    (Pessoa)-[:COMPROU]->(Produto)

// Anti-padrão: listas gigantes
// como propriedade
// ERRADO:  p.amigos = [1,2,...1M]
// CERTO:   (p)-[:AMIGO]->(a)

// Relações > arrays de ids!

Boas práticas de modelação: nós = entidades, relações = verbos com direção natural, labels CamelCase, tipos MAIÚSCULAS. Nunca uses arrays gigantes como propriedades — relações são a estrutura nativa do grafo.

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

// Com MATCH (cruzamento):
UNWIND ["Ana", "Rui"] AS nome
MATCH (p:Pessoa {nome: nome})
RETURN p

// Lista de maps → nós:
UNWIND [{n: "Ana"}, {n: "Rui"}] AS d
MERGE (:Pessoa {nome: d.n})

// Reverter collect:
MATCH (p:Pessoa)
WITH collect(p) AS todos
UNWIND todos AS p
RETURN p.nome

UNWIND expande listas em linhas (inverso de collect()). Combina com MATCH para pesquisar vários valores e com MERGE para criar nós a partir de listas de maps. Base de imports.

LOAD CSV (import)
// Importar CSV (ficheiro local
// em import/ ou URL):
LOAD CSV WITH HEADERS
FROM "file:///pessoas.csv" AS linha
MERGE (p:Pessoa {email: linha.email})
SET p.nome = linha.nome,
    p.idade = toInteger(linha.idade)

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

// CSV remoto:
FROM "https://exemplo.com/dados.csv"

LOAD CSV WITH HEADERS importa CSVs linha a linha (cada linha = map). Combina com MERGE para imports idempotentes e toInteger() para converter tipos. Suporta URLs e delimitador custom com FIELDTERMINATOR.

Parâmetros ($param)
// Em vez de hardcode:
// MATCH (p {nome: "Ana"})

// Usa parâmetro:
MATCH (p:Pessoa {nome: $nome})
RETURN p

// No Browser, define antes:
:param nome => "Ana"

// Vários:
:param params => {
  nome: "Ana", idade: 30
}

// Em drivers: passa no execute()
// Previne injeção de Cypher!

Parâmetros ($nome) separam dados do código — previnem injeção de Cypher e permitem reutilizar queries. No Browser define com :param nome => "Ana"; em drivers, passa no método de execução.

Import completo (exemplo)
// 1. Nós (com constraint primeiro):
CREATE CONSTRAINT FOR (p:Pessoa)
REQUIRE p.email IS UNIQUE

LOAD CSV WITH HEADERS
FROM "file:///pessoas.csv" AS l
MERGE (p:Pessoa {email: l.email})
SET p.nome = l.nome

// 2. Relações:
LOAD CSV WITH HEADERS
FROM "file:///amizades.csv" AS l
MATCH (a:Pessoa {email: l.de})
MATCH (b:Pessoa {email: l.para})
MERGE (a)-[:AMIGA_DE]->(b)

// Ordem: constraints → nós
// → relações

Import real em 3 passos: cria constraints primeiro (unicidade), depois nós com MERGE, por fim relações casando os nós por chave. Esta ordem garante integridade e performance.

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

// EXISTS subquery:
MATCH (p:Pessoa)
WHERE EXISTS {
  MATCH (p)-[:TEM]->(:Carro)
}
RETURN p.nome

CALL { ... } (Neo4j 4.1+) executa subqueries correlacionadas — usa WITH p para importar variáveis do exterior. Permite agregações por linha e filtros complexos impossíveis com MATCH simples.

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

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

// Export via APOC:
CALL apoc.export.cypher.all(
  "backup.cypher", {}
)
// gera script Cypher re-executável

// Export só resultados:
CALL apoc.export.csv.query(
  "MATCH (p:Pessoa) RETURN p",
  "pessoas.csv", {}
)

Backup completo com neo4j-admin database dump/load. APOC export gera scripts Cypher re-executáveis ou CSV de queries específicas. Faz dumps regulares antes de alterações grandes.