How to read this page. Ten steps, one query at a time. For each: the query to type, what Neo4j Browser displays, and what to look at. Type each query yourself. The "To understand better" blocks are optional. If the lab is not started or if
etatdoes not saynœuds : 872, go back to the guided practice, In Brief section (kit:https://github.com/hrhouma2/aiopsatlas-recherche-graphes-labo-fr).
In the guided practice, you counted 872 nodes and 3,712 relationships loaded by a script, and you looked at bubbles and arrows that someone else had drawn. Here, you draw your own. An empty node first, to see that a node is just that; then you give it a name, an age, a city; then a second node, and an arrow between the two; then an arrow toward a course from the real dataset. At the end, the etat counter goes up to 874, and you bring it back to 872 by deleting what you created, with the error you need to have seen once to understand what a relationship is.
A node is a bubble. A label says what kind of bubble it is (Personne, Cours, Ville): it is the equivalent of the table name. A property is a value written inside the bubble (nom: "Alice", age: 30): a column. A relationship is an arrow between two bubbles; it has a type in capital letters (CONNAIT, AIME), a direction, and can carry its own properties. In SQL, the arrow would be a foreign key or a join table; here it is a first-class object, which you create, read, and delete like a node.
| Neo4j | Classic SQL database | In this practice |
|---|---|---|
| node | row | Alice, Bob |
| label | table | Personne |
| property | column | nom, age, ville |
| relationship | foreign key / join table | CONNAIT, AIME |
| relationship type | name of the join table | CONNAIT |
| relationship property | column of the join table | depuis: 2024 |
Two Cypher writing rules to remember: a bubble is written between parentheses (p:Personne), an arrow between square brackets with a dash and an arrowhead -[:CONNAIT]->. The letter before the colon (p, a, r) is a temporary name you choose to reuse the object within the same query.
Open http://localhost:7474, connect (neo4j / aiopsatlas2026, URL localhost:7687). The editor is the neo4j$ line at the top. You send with Ctrl + Enter (Cmd + Enter on macOS) or the ▶ button on the right. Each query opens a frame under the editor, the most recent at the top.
A frame displays two things. At the top, a green summary line: Created 1 node, added 1 label, Set 1 property, Deleted 2 nodes… It is the one that tells you what the query changed. Below, if the query has a RETURN, the result in three views: Graph (the bubbles and arrows), Table (the values in columns), Raw (the raw JSON). A query without RETURN only displays the summary and Completed after … ms.
CREATE (p:Personne) RETURN pWhat the query asks: create a bubble carrying the Personne label, call it p for the duration of the query, and show it to me.
Summary: Created 1 node, added 1 label, then Started streaming 1 record after 1 ms and completed after 2 ms.
What to look at: Graph view, a single bubble, and on the right in Results overview: Nodes (1) with the Personne (1) chip. Click the bubble: the panel lists no property. It is a minimal node: a label, nothing else. SQL equivalent: INSERT INTO personne DEFAULT VALUES, in a table that would have no columns.
CREATE (n) without a label, works too. Neo4j accepts it, but you could only find it again by going through every bubble in the graph: a row without a table. Do not do it; a label, always.Personne is a new label in this graph, which only knew five (Competence, Cours, Etudiant, Professeur, Ville). You did not have to declare anything: the first bubble that carries a label creates it. Like the Elasticsearch mapping, in foundational workshop 1.RETURN p is not mandatory. Without it, the query creates the bubble and only displays the summary. It is there so that you can see it.MATCH (p:Personne) SET p.nom = 'Alice' RETURN pWhat the query asks: find the Personne bubbles (there is only one), write nom = 'Alice' inside it, show it.
Summary: Set 1 property.
What to look at: the bubble now carries Alice as its caption (Browser displays the first text property by default). Click it: nom: Alice. SQL equivalent: UPDATE personne SET nom = 'Alice'.
MATCH before SET. To modify a bubble, you first have to find it. MATCH (p:Personne) is the SELECT … FROM personne; SET is the UPDATE. Cypher chains the two in the same sentence.'Alice' (double quotes work too). Numbers are written bare: 30, not '30'.nom column for Bob later on: he simply will not have a ville property.MATCH (p:Personne {nom: 'Alice'}) SET p.age = 30, p.ville = 'Montréal' RETURN pWhat the query asks: find the Personne bubble whose nom is Alice, write two more values.
Summary: Set 2 properties.
What to look at: the bubble, when clicked, shows nom: Alice, age: 30, ville: Montréal. The {nom: 'Alice'} between braces is a filter: "the one whose nom is Alice", the WHERE nom = 'Alice' of SQL. It changes nothing here (a single Personne), but from the next step on it becomes indispensable.
CREATE (b:Personne {nom: 'Bob', age: 25}) RETURN bWhat the query asks: create a Personne bubble that immediately has nom and age.
Summary: Created 1 node, set 2 properties, added 1 label.
Now look at your two bubbles as a table:
MATCH (p:Personne) RETURN p.nom, p.age, p.villeTable view:
p.nom p.age p.ville
"Alice" 30 "Montréal"
"Bob" 25 nullWhat to look at: null for Bob's city: he does not have this property. In SQL it would be an empty cell in the ville column; here, the cell does not exist at all. SQL equivalent: SELECT nom, age, ville FROM personne.
MATCH (a:Personne {nom: 'Alice'}), (b:Personne {nom: 'Bob'})
CREATE (a)-[:CONNAIT]->(b)What the query asks: find Alice, call her a; find Bob, call him b; create an arrow of type CONNAIT that starts from a and lands on b.
Summary: Created 1 relationship, then Completed after … ms. No result: there is no RETURN.
Look at the arrow:
MATCH (a:Personne)-[r]->(b:Personne) RETURN a, r, bWhat the query asks: all arrows, whatever their type ([r] without :TYPE), that go from a Personne to a Personne.
What to look at: Graph view, two bubbles and one arrow with CONNAIT written on it, in the direction Alice → Bob. On the right, Results overview: Nodes (2), Relationships (1) with the CONNAIT (1) chip. SQL equivalent: INSERT INTO connait (personne_source, personne_cible) VALUES (id_alice, id_bob), in a join table you would have had to create beforehand.
MATCH on the first line: without it, CREATE (a)-[:CONNAIT]->(b) would create two new, empty bubbles and an arrow between them. Classic mistake._: CONNAIT, A_SUIVI, ENSEIGNE. It is not mandatory, it is what everyone does.(a)-[:CONNAIT]->(b): the arrowhead is on b. There is no undirected relationship in Neo4j; when reading, however, you can ignore the direction (step 7).MATCH (:Personne {nom: 'Alice'})-[r:CONNAIT]->(:Personne {nom: 'Bob'})
SET r.depuis = 2024
RETURN rWhat the query asks: find the CONNAIT arrow that goes from Alice to Bob, call it r, write depuis = 2024 on it.
Summary: Set 1 property.
Read everything as a table:
MATCH (a:Personne)-[r:CONNAIT]->(b:Personne)
RETURN a.nom, type(r), r.depuis, b.noma.nom type(r) r.depuis b.nom
"Alice" "CONNAIT" 2024 "Bob"What to look at: type(r) gives the arrow's type; r.depuis its property, exactly like p.nom for a bubble. A relationship is handled like a node: you find it, you name it, you read from and write to it. Bubbles without a temporary name, (:Personne {…}), are those you do not need afterward: you can omit the letter.
MATCH (:Personne {nom: 'Bob'})-[:CONNAIT]->(x) RETURN x.nomWhat the query asks: whom does Bob know? The x bubbles at the end of a CONNAIT arrow that starts from Bob.
(no changes, no records)What to look at: zero rows. The only arrow goes from Alice to Bob; none starts from Bob. Now without the arrowhead:
MATCH (:Personne {nom: 'Bob'})-[:CONNAIT]-(x) RETURN x.nomx.nom
"Alice"-[:CONNAIT]- without >: "a CONNAIT arrow between Bob and x, in one direction or the other". Alice appears. Remember: when writing, the direction is mandatory; when reading, you choose whether to take it into account or not. This is what makes "who is linked to whom" so short to write in Cypher.
MATCH (a:Personne {nom: 'Alice'}), (c:Cours {id: 'C0001'})
CREATE (a)-[:AIME]->(c)What the query asks: find Alice, find course C0001 from the dataset, create an AIME arrow from one to the other.
Summary: Created 1 relationship.
Alice is now linked to a bubble that existed before you. Look at everything that starts from her:
MATCH (a:Personne {nom: 'Alice'})-[r]->(x)
RETURN a.nom, type(r), labels(x)[0] AS type_cible, coalesce(x.nom, x.titre) AS ciblea.nom type(r) type_cible cible
"Alice" "AIME" "Cours" "Docker expliqué simplement"
"Alice" "CONNAIT" "Personne" "Bob"What to look at: two rows, two arrow types, two target types. labels(x)[0] gives the label of the bubble at the end; coalesce(x.nom, x.titre) takes nom if it exists, otherwise titre (Bob has a nom, the course has a titre). This is your deliverable table: keep it. In SQL, it would have taken one join per target table and a UNION; here, a single MATCH line.
Switch to Graph view on MATCH (a:Personne {nom: 'Alice'})-[r]->(x) RETURN a, r, x: Alice in the center, Bob on one side, the course on the other, in two colors (one per label).
MATCH (n) RETURN count(n) AS noeudsnoeuds
874872 + your 2 bubbles. By label:
MATCH (n) RETURN labels(n)[0] AS label, count(*) ORDER BY labellabel count(*)
"Competence" 22
"Cours" 504
"Etudiant" 300
"Personne" 2
"Professeur" 30
"Ville" 16What to look at: a sixth row, Personne 2. In a terminal, .\labo.ps1 etat or ./labo.sh etat now shows ✔ Neo4j répond — nœuds : 874. The script and Neo4j Browser count the same thing by two paths: it is the cross-check from the guided practice, applied to what you created yourself.
First try the naive deletion:
MATCH (p:Personne) DELETE pNeo.ClientError.Schema.ConstraintValidationFailed
Cannot delete node<874>, because it still has relationships. To delete this node, you must first delete its relationships.What to look at: a red frame, nothing deleted. Neo4j refuses to erase a bubble that still has arrows: an arrow with no bubble at its end would make no sense. The number node<874> is the internal identifier of the first bubble it tried to delete; on your machine it differs. It is the equivalent of a foreign key violation in SQL, with one difference: here, the remedy fits in one word.
MATCH (p:Personne) DETACH DELETE pWhat the query asks: find the Personne bubbles, detach them (delete their arrows), then delete them.
Summary: Deleted 2 nodes, deleted 2 relationships.
Two nodes, two relationships: CONNAIT and AIME. Course C0001 was not touched; only the arrow that led to it disappeared. Verify:
MATCH (n) RETURN count(n) AS noeudsnoeuds
872MATCH (n) RETURN labels(n)[0] AS label, count(*) ORDER BY label again returns five rows, without Personne, and etat says nœuds : 872.
DETACH DELETE is the normal move to delete a node in Neo4j; DELETE alone is only used for a node you know is isolated, or to delete a relationship: MATCH ()-[r:AIME]->() DELETE r erases the arrow and keeps the bubbles.MERGE instead of CREATE: "create if it does not exist, otherwise reuse". This is what allows the charger-graphe script to be replayed without duplicates. With CREATE, rerunning step 4 would have manufactured a second Bob.DELETE pratique-mini in foundational workshop 1. Make it a habit: what you create to learn, you delete at the end.nom, then age and ville, with SET, by first finding it with MATCH.null where a property is missing.CONNAIT between two existing bubbles, put depuis on it, and read type(r) and r.depuis in a table.(Bob)-[:CONNAIT]->(x) returns nothing and (Bob)-[:CONNAIT]-(x) returns Alice.C0001 and kept the two-row table of step 8 as the deliverable.Cannot delete node … because it still has relationships, then DETACH DELETE returned Deleted 2 nodes, deleted 2 relationships.MATCH (n) RETURN count(n) and etat say 872.Invalid input, expected … in red → syntax error. The three causes seen in class: a parenthesis ( closed by a bracket ], an arrowhead forgotten when writing (-[:CONNAIT]- in a CREATE is refused: Only directed relationships are supported in CREATE), a missing quote around a text.Variable `r` not defined (or a, p…) → you are using a letter in SET or RETURN that no MATCH or CREATE in the same query has introduced. Example: SET r.depuis = 2024 while the arrow was written -[:CONNAIT]-> without the r. Add the letter where the object appears: -[r:CONNAIT]->.Created 2 nodes, created 1 relationship, and Results overview shows * (4)) → you typed CREATE (a)-[:CONNAIT]->(b) without the MATCH right before, or with a semicolon between the two lines. a and b were unknown: Neo4j does not protest, it invents them, with no label or property. Clean up: MATCH (n) WHERE size(labels(n)) = 0 DETACH DELETE n, then redo step 5 as a single query.MATCH (p:Personne) RETURN count(p) returns more than 2) → you reran a CREATE. MATCH (p:Personne) DETACH DELETE p and start again at step 1; module 6 will teach you MERGE, which avoids this.Cannot delete node … because it still has relationships → this is step 10, intended. DETACH DELETE.<id> instead of Alice → Browser does not yet know which property to show. Click the Personne chip under the graph and choose nom as the caption; or read in Table view.etat still says nœuds : 872 after your creations → you are connected to another database or another Neo4j (a neo4j installed locally on port 7687?). In Browser, the top banner must say neo4j://localhost:7687 and Database: neo4j; otherwise, close the other Neo4j or change its port.Neo.ClientError.Security.Unauthorized → password refused. It is aiopsatlas2026; if you changed it and lost it, ./labo.sh reinitialiser then demarrer and charger-graphe (the kit data is reloaded identically).