Atelier fundamental 2 — Neo4j: um nó, propriedades, uma relação

Prática guiada15 min
Duração
25 min
Módulo
1/7
Pré-requisitos
o laboratório roda (etat exibe nœuds : 872), Neo4j Browser aberto e conectado
Você vai construir
dois nós seus, Alice e Bob, ligados por uma seta, depois ligados ao grafo real do curso, antes de apagar tudo corretamente
Entregável
a tabela da etapa 8 (Alice, suas duas relações, seus alvos)

Como ler esta página. Dez etapas, uma consulta por vez. Para cada uma: a consulta a digitar, o que o Neo4j Browser exibe, e o que é preciso olhar. Digite você mesmo cada consulta. Os blocos "Para entender bem" são opcionais. Se o laboratório não estiver iniciado ou se etat não disser nœuds : 872, volte à prática guiada, seção Em resumo (kit: https://github.com/hrhouma2/aiopsatlas-recherche-graphes-labo-fr).

Objetivo

Na prática guiada, você contou 872 nós e 3.712 relações carregados por um script, e olhou bolhas e setas que outra pessoa havia desenhado. Aqui, você desenha as suas. Um nó vazio primeiro, para ver que um nó é só isso; depois você lhe dá um nome, uma idade, uma cidade; depois um segundo nó, e uma seta entre os dois; depois uma seta para um curso do conjunto de dados real. No fim, o contador de etat sobe para 874, e você o traz de volta a 872 excluindo o que criou, com o erro que é preciso ter visto uma vez para entender o que é uma relação.

O vocabulário em uma imagem

Um é uma bolha. Um rótulo diz de que tipo de bolha se trata (Personne, Cours, Ville): é o equivalente do nome da tabela. Uma propriedade é um valor escrito na bolha (nom: "Alice", age: 30): uma coluna. Uma relação é uma seta entre duas bolhas; ela tem um tipo em maiúsculas (CONNAIT, AIME), um sentido, e pode carregar suas próprias propriedades. Em SQL, a seta seria uma chave estrangeira ou uma tabela de junção; aqui é um objeto por inteiro, que você cria, lê e exclui como um nó.

Neo4jBanco SQL clássicoNesta prática
linhaAlice, Bob
rótulotabelaPersonne
propriedadecolunanom, age, ville
relaçãochave estrangeira / tabela de junçãoCONNAIT, AIME
tipo de relaçãonome da tabela de junçãoCONNAIT
propriedade de relaçãocoluna da tabela de junçãodepuis: 2024

Duas regras de escrita Cypher para memorizar: uma bolha se escreve entre parênteses (p:Personne), uma seta entre colchetes com um traço e uma ponta -[:CONNAIT]->. A letra antes dos dois-pontos (p, a, r) é um nome temporário que você escolhe para reutilizar o objeto na mesma consulta.

Onde digitar, e como ler a resposta

Abra http://localhost:7474, conecte-se (neo4j / aiopsatlas2026, URL localhost:7687). O editor é a linha neo4j$ no alto. Você envia com Ctrl + Enter (Cmd + Enter no macOS) ou o botão ▶ à direita. Cada consulta abre um quadro sob o editor, o mais recente em cima.

Um quadro exibe duas coisas. No alto, uma linha verde de balanço: Created 1 node, added 1 label, Set 1 property, Deleted 2 nodes… É ela que lhe diz o que a consulta mudou. Embaixo, se a consulta tem um RETURN, o resultado em três visões: Graph (as bolhas e as setas), Table (os valores em colunas), Raw (o JSON bruto). Uma consulta sem RETURN exibe só o balanço e Completed after … ms.

Etapa 1 — Criar um nó, só um rótulo

cypher
CREATE (p:Personne) RETURN p

O que a consulta pede: crie uma bolha com o rótulo Personne, chame-a p durante a consulta, e mostre-a para mim.

Balanço: Created 1 node, added 1 label, depois Started streaming 1 record after 1 ms and completed after 2 ms.

O que olhar: visão Graph, uma única bolha, e à direita em Results overview: Nodes (1) com a pastilha Personne (1). Clique na bolha: o painel não lista nenhuma propriedade. É um nó mínimo: um rótulo, nada mais. Equivalente SQL: INSERT INTO personne DEFAULT VALUES, em uma tabela que não teria nenhuma coluna.

Para entender bem
  • Ainda mais mínimo, CREATE (n) sem rótulo, também funciona. O Neo4j o aceita, mas você só poderia reencontrá-la passando por todas as bolhas do grafo: uma linha sem tabela. Não faça isso; um rótulo, sempre.
  • Personne é um rótulo novo neste grafo, que só conhecia cinco (Competence, Cours, Etudiant, Professeur, Ville). Você não precisou declarar nada: a primeira bolha que carrega um rótulo o cria. Como o mapping do Elasticsearch, na oficina fundamental 1.
  • RETURN p não é obrigatório. Sem ele, a consulta cria a bolha e exibe só o balanço. Ele está lá para que você a veja.

Etapa 2 — Dar-lhe um valor

cypher
MATCH (p:Personne) SET p.nom = 'Alice' RETURN p

O que a consulta pede: encontre as bolhas Personne (só há uma), escreva dentro nom = 'Alice', mostre-a.

Balanço: Set 1 property.

O que olhar: a bolha agora tem Alice como legenda (o Browser exibe por padrão a primeira propriedade de texto). Clique nela: nom: Alice. Equivalente SQL: UPDATE personne SET nom = 'Alice'.

Para entender bem
  • MATCH antes de SET. Para modificar uma bolha, é preciso primeiro encontrá-la. MATCH (p:Personne) é o SELECT … FROM personne; SET é o UPDATE. O Cypher encadeia os dois na mesma frase.
  • Os textos ficam entre apóstrofos simples 'Alice' (as aspas duplas também funcionam). Os números se escrevem sem nada: 30, não '30'.
  • Uma propriedade só existe se tem um valor. Não há coluna nom vazia em Bob daqui a pouco: ele simplesmente não terá a propriedade ville.

Etapa 3 — Vários valores de uma vez

cypher
MATCH (p:Personne {nom: 'Alice'}) SET p.age = 30, p.ville = 'Montréal' RETURN p

O que a consulta pede: encontre a bolha Personne cujo nom é Alice, escreva dois valores a mais.

Balanço: Set 2 properties.

O que olhar: a bolha, ao clicar, mostra nom: Alice, age: 30, ville: Montréal. O {nom: 'Alice'} entre chaves é um filtro: "aquela cujo nom vale Alice", o WHERE nom = 'Alice' do SQL. Ele não muda nada aqui (uma única Personne), mas a partir da próxima etapa se torna indispensável.

Etapa 4 — Criar um nó com seus valores de uma vez

cypher
CREATE (b:Personne {nom: 'Bob', age: 25}) RETURN b

O que a consulta pede: crie uma bolha Personne que já tenha nom e age.

Balanço: Created 1 node, set 2 properties, added 1 label.

Olhe agora suas duas bolhas em tabela:

cypher
MATCH (p:Personne) RETURN p.nom, p.age, p.ville

Visão Table:

text
p.nom    p.age  p.ville
"Alice"  30     "Montréal"
"Bob"    25     null

O que olhar: null para a cidade de Bob: ele não tem essa propriedade. Em SQL seria uma célula vazia da coluna ville; aqui, o quadro não existe de forma alguma. Equivalente SQL: SELECT nom, age, ville FROM personne.

Etapa 5 — Ligar Alice a Bob

cypher
MATCH (a:Personne {nom: 'Alice'}), (b:Personne {nom: 'Bob'})
CREATE (a)-[:CONNAIT]->(b)

O que a consulta pede: encontre Alice, chame-a a; encontre Bob, chame-o b; crie uma seta do tipo CONNAIT que parte de a e chega em b.

Balanço: Created 1 relationship, depois Completed after … ms. Sem resultado: não há RETURN.

Olhe a seta:

cypher
MATCH (a:Personne)-[r]->(b:Personne) RETURN a, r, b

O que a consulta pede: todas as setas, qualquer que seja seu tipo ([r] sem :TIPO), que vão de uma Personne a uma Personne.

O que olhar: visão Graph, duas bolhas e uma seta com CONNAIT escrito em cima, no sentido Alice → Bob. À direita, Results overview: Nodes (2), Relationships (1) com a pastilha CONNAIT (1). Equivalente SQL: INSERT INTO connait (personne_source, personne_cible) VALUES (id_alice, id_bob), em uma tabela de junção que você deveria ter criado antes.

Para entender bem
  • Uma relação liga duas bolhas que já existem. Daí o MATCH na primeira linha: sem ele, CREATE (a)-[:CONNAIT]->(b) criaria duas bolhas novas e vazias e uma seta entre elas. Erro clássico.
  • O tipo é em MAIÚSCULAS por convenção, com _: CONNAIT, A_SUIVI, ENSEIGNE. Não é obrigatório, é o que todo mundo faz.
  • Uma relação sempre tem um sentido. (a)-[:CONNAIT]->(b): a ponta está em b. Não existe relação sem direção no Neo4j; na leitura, por outro lado, pode-se ignorar o sentido (etapa 7).

Etapa 6 — Um valor na seta

cypher
MATCH (:Personne {nom: 'Alice'})-[r:CONNAIT]->(:Personne {nom: 'Bob'})
SET r.depuis = 2024
RETURN r

O que a consulta pede: encontre a seta CONNAIT que vai de Alice a Bob, chame-a r, escreva nela depuis = 2024.

Balanço: Set 1 property.

Leia tudo em tabela:

cypher
MATCH (a:Personne)-[r:CONNAIT]->(b:Personne)
RETURN a.nom, type(r), r.depuis, b.nom
text
a.nom    type(r)    r.depuis  b.nom
"Alice"  "CONNAIT"  2024      "Bob"

O que olhar: type(r) dá o tipo da seta; r.depuis sua propriedade, exatamente como p.nom para uma bolha. Uma relação se trata como um nó: encontramos, nomeamos, lemos e escrevemos dentro. As bolhas sem nome temporário, (:Personne {…}), são aquelas de que você não precisa depois: você pode omitir a letra.

Etapa 7 — A seta tem um sentido

cypher
MATCH (:Personne {nom: 'Bob'})-[:CONNAIT]->(x) RETURN x.nom

O que a consulta pede: quem Bob conhece? As bolhas x na ponta de uma seta CONNAIT que parte de Bob.

text
(no changes, no records)

O que olhar: zero linha. A única seta vai de Alice para Bob; nenhuma parte de Bob. Agora sem a ponta:

cypher
MATCH (:Personne {nom: 'Bob'})-[:CONNAIT]-(x) RETURN x.nom
text
x.nom
"Alice"

-[:CONNAIT]- sem >: "uma seta CONNAIT entre Bob e x, em um sentido ou no outro". Alice aparece. Memorize: na escrita, o sentido é obrigatório; na leitura, você escolhe levá-lo em conta ou não. É o que torna "quem está ligado a quem" tão curto de escrever em Cypher.

Etapa 8 — Ligar sua bolha ao grafo real

cypher
MATCH (a:Personne {nom: 'Alice'}), (c:Cours {id: 'C0001'})
CREATE (a)-[:AIME]->(c)

O que a consulta pede: encontre Alice, encontre o curso C0001 do conjunto de dados, crie uma seta AIME de uma para o outro.

Balanço: Created 1 relationship.

Alice agora está ligada a uma bolha que existia antes de você. Olhe tudo o que parte dela:

cypher
MATCH (a:Personne {nom: 'Alice'})-[r]->(x)
RETURN a.nom, type(r), labels(x)[0] AS type_cible, coalesce(x.nom, x.titre) AS cible
text
a.nom    type(r)    type_cible  cible
"Alice"  "AIME"     "Cours"     "Docker expliqué simplement"
"Alice"  "CONNAIT"  "Personne"  "Bob"

O que olhar: duas linhas, dois tipos de seta, dois tipos de alvo. labels(x)[0] dá o rótulo da bolha na ponta; coalesce(x.nom, x.titre) pega nom se existir, senão titre (Bob tem um nom, o curso tem um titre). Esta é a sua tabela-entregável: guarde-a. Em SQL, seria preciso uma junção por tabela de alvo e um UNION; aqui, uma única linha de MATCH.

Passe para a visão Graph em MATCH (a:Personne {nom: 'Alice'})-[r]->(x) RETURN a, r, x: Alice no centro, Bob de um lado, o curso do outro, em duas cores (uma por rótulo).

Etapa 9 — Contar

cypher
MATCH (n) RETURN count(n) AS noeuds
text
noeuds
874

872 + suas 2 bolhas. Por rótulo:

cypher
MATCH (n) RETURN labels(n)[0] AS label, count(*) ORDER BY label
text
label         count(*)
"Competence"  22
"Cours"       504
"Etudiant"    300
"Personne"    2
"Professeur"  30
"Ville"       16

O que olhar: uma sexta linha, Personne 2. Em um terminal, .\labo.ps1 etat ou ./labo.sh etat exibe agora ✔ Neo4j répond — nœuds : 874. O script e o Neo4j Browser contam a mesma coisa por dois caminhos: é a verificação cruzada da prática guiada, aplicada ao que você mesmo criou.

Etapa 10 — Limpar, e encontrar o erro que explica tudo

Experimente primeiro a exclusão ingênua:

cypher
MATCH (p:Personne) DELETE p
text
Neo.ClientError.Schema.ConstraintValidationFailed
Cannot delete node<874>, because it still has relationships. To delete this node, you must first delete its relationships.

O que olhar: um quadro vermelho, nada excluído. O Neo4j se recusa a apagar uma bolha que ainda tem setas: uma seta sem bolha na ponta não teria sentido. O número node<874> é o identificador interno da primeira bolha que ele tentou excluir; na sua máquina ele difere. É o equivalente de uma violação de chave estrangeira em SQL, com uma diferença: aqui, o remédio cabe em uma palavra.

cypher
MATCH (p:Personne) DETACH DELETE p

O que a consulta pede: encontre as bolhas Personne, desprenda-as (exclua suas setas), depois exclua-as.

Balanço: Deleted 2 nodes, deleted 2 relationships.

Dois nós, duas relações: CONNAIT e AIME. O curso C0001 não foi tocado, só a seta que levava a ele desapareceu. Verifique:

cypher
MATCH (n) RETURN count(n) AS noeuds
text
noeuds
872

MATCH (n) RETURN labels(n)[0] AS label, count(*) ORDER BY label devolve novamente cinco linhas, sem Personne, e etat diz nœuds : 872.

Para entender bem
  • DETACH DELETE é o gesto normal para excluir um nó no Neo4j; DELETE sozinho só serve para um nó que você sabe estar isolado, ou para excluir uma relação: MATCH ()-[r:AIME]->() DELETE r apaga a seta e guarda as bolhas.
  • O módulo 6 fará você escrever MERGE no lugar de CREATE: "crie se não existir, senão reutilize". É o que permite ao script charger-graphe ser reexecutado sem duplicata. Com CREATE, reexecutar a etapa 4 teria fabricado um segundo Bob.
  • Nada do que você fez ficou. O grafo voltou ao seu estado de referência, exatamente como após DELETE pratique-mini na oficina fundamental 1. Adquira o hábito: o que você cria para aprender, você exclui no fim.

Verificação final

  • Você criou um nó com um único rótulo e viu que ele não tem nenhuma propriedade.
  • Você lhe deu nom, depois age e ville, com SET, encontrando-o primeiro com MATCH.
  • Você criou Bob com seus valores em uma consulta, e viu null onde uma propriedade falta.
  • Você criou CONNAIT entre duas bolhas existentes, colocou depuis nela, e leu type(r) e r.depuis em tabela.
  • Você sabe dizer por que (Bob)-[:CONNAIT]->(x) não devolve nada e (Bob)-[:CONNAIT]-(x) devolve Alice.
  • Você ligou Alice ao curso C0001 e guardou a tabela de duas linhas da etapa 8 como entregável.
  • Você viu o erro Cannot delete node … because it still has relationships, depois DETACH DELETE devolveu Deleted 2 nodes, deleted 2 relationships.
  • MATCH (n) RETURN count(n) e etat dizem 872.

Se travar

Mostrar os casos frequentes
  • Invalid input, expected … em vermelho → erro de sintaxe. As três causas em sala: um parêntese ( fechado por um colchete ], uma ponta de seta esquecida na escrita (-[:CONNAIT]- em um CREATE é recusado: Only directed relationships are supported in CREATE), um apóstrofo faltando em volta de um texto.
  • Variable `r` not defined (ou a, p…) → você usa uma letra em SET ou RETURN que nenhum MATCH nem CREATE da mesma consulta introduziu. Exemplo: SET r.depuis = 2024 quando a seta foi escrita -[:CONNAIT]-> sem o r. Acrescente a letra onde o objeto aparece: -[r:CONNAIT]->.
  • A etapa 5 criou bolhas vazias a mais (Created 2 nodes, created 1 relationship, e Results overview mostra * (4)) → você digitou CREATE (a)-[:CONNAIT]->(b) sem o MATCH logo antes, ou com um ponto e vírgula entre as duas linhas. a e b eram desconhecidos: o Neo4j não protesta, ele os inventa, sem rótulo nem propriedade. Limpe: MATCH (n) WHERE size(labels(n)) = 0 DETACH DELETE n, depois retome a etapa 5 em uma única consulta.
  • Duas Alice ou dois Bob (MATCH (p:Personne) RETURN count(p) devolve mais de 2) → você reexecutou um CREATE. MATCH (p:Personne) DETACH DELETE p e retome na etapa 1; o módulo 6 ensinará MERGE, que evita isso.
  • Cannot delete node … because it still has relationships → é a etapa 10, intencional. DETACH DELETE.
  • A bolha exibe um número ou <id> em vez de Alice → o Browser ainda não sabe qual propriedade mostrar. Clique na pastilha Personne sob o grafo e escolha nom como legenda; ou leia na visão Table.
  • etat continua dizendo nœuds : 872 após suas criações → você está conectado a outro banco ou outro Neo4j (um neo4j instalado localmente na porta 7687?). No Browser, a faixa do alto deve dizer neo4j://localhost:7687 e Database: neo4j; senão, feche o outro Neo4j ou mude sua porta.
  • Neo.ClientError.Security.Unauthorized → senha recusada. É aiopsatlas2026; se você a mudou e perdeu, ./labo.sh reinitialiser depois demarrer e charger-graphe (os dados do kit são recarregados de forma idêntica).