Cypher 2 - You Do Not Have to Know How Many Steps
In post 1 we chained -[:KNOWS]-> twice to find a friend of
a friend. More often, though, you do not know how many steps it takes.
Here we write that kind of path in one line.
1. Say the data looks like this
People, restaurants and a city, joined by edges.
me likes Mina, and Mina collaborated with Olive Table. Jun trained
under Mina and built the wine list at Cork and Candle.
We want restaurants in Lisbon connected to someone I like. It might be one step away, it might be three.
2. One query
MATCH (me:Person {id: $userId})-[:LIKES]->(s:Person)
MATCH path = (s)-[:TRAINED_UNDER|COLLAB_WITH|BUILT_WINE_LIST*1..3]-(p:Place)
-[:IN]->(:City {name: 'Lisbon'})
RETURN p.name,
[r IN relationships(path) | type(r)] AS why,
length(path) AS hops
ORDER BY hops
LIMIT 3
Only four things are new since post 1.
path = name the whole path that was found
[:A|B|C] an A or a B or a C edge
*1..3 one to three edges along the way -- distance 1 to 3
relationships(path) the list of edges on the path
The rest came up in post 1: $userId, two MATCH clauses, a node with no name
tag, [x IN list | ...], AS, ORDER BY, LIMIT.
3. Line by line
The first MATCH
(me:Person {id: $userId}) the Person whose id is $userId, called me
-[:LIKES]->(s:Person) whoever me LIKES, called s
In the picture, s is Mina.
The second MATCH
This is the heart of the post.
*1..3 distance 1 to 3 will do. You need not know which
the end is - not -> direction is ignored
[:A|B|C] any of the three types
(p:Place) the destination is a restaurant, tagged p
-[:IN]->(:City {...}) it has to be in Lisbon
Without *1..3 you would write one query for one step, another for two and
another for three. And as post 1 showed, you may mix the three types —
that is how path B below qualifies.
The - at the end also means you can walk backwards. The edge saying Jun
trained under Mina points Jun → Mina, but you can start at Mina and go
back up it to Jun.
The city is never returned, so (:City ...) has no name tag — the same slot
you saw in post 1.
Two paths qualify.
A Mina --COLLAB_WITH--> Olive Table --IN--> Lisbon 2 edges
B Mina <--TRAINED_UNDER-- Jun
--BUILT_WINE_LIST--> Cork and Candle --IN--> Lisbon 3 edges
RETURN
p.name the restaurant name
relationships(path) the list of edges on the path
[r IN list | type(r)] take only the type name of each edge
length(path) how many edges the whole path has -- the distance
4. The result
p.name why hops
"Olive Table" ["COLLAB_WITH", "IN"] 2
"Cork and Candle" ["TRAINED_UNDER", "BUILT_WINE_LIST", "IN"] 3
hops looks like it exceeds the range in *1..3, and it does. The star
binds only the middle segment, while length(path) counts the closing
-[:IN]-> as well. Path B is two in the middle plus the one IN.
why is the useful column. Hand back only a restaurant name and you cannot
explain why you recommended it; with the edge types alongside, you can say
"Olive Table collaborated with Mina, who you like."
5. In SQL
That single *1..3 becomes a one-step JOIN, a two-step JOIN and a three-step
JOIN, combined with UNION — or you reach for WITH RECURSIVE. Add the
three edge types and the ignored direction and it grows further.
A note
Raising the numbers in *1..3 gets slow. The number of walkable paths
grows at every step. Better to write down a range you will actually use.