Skip to the content.

Relationships

Graph traversal patterns using typed edge operators.

Basic Relationship Match

type Person = { Name: string; Age: int }
type Movie = { Title: string; Released: int }
type ActedIn = { Roles: string list }

let query = cypher {
    for p in node<Person> do
    for m in node<Movie> do
    matchRel (p -- edge<ActedIn> --> m)
    where (p.Age > 30)
    select (p.Name, m.Title)
}
// MATCH (p:Person) MATCH (m:Movie)
// MATCH (p)-[:ACTED_IN]->(m)
// WHERE p.age > $p0 RETURN p.name, m.title

The relationship type is extracted from the F# type name and converted to UPPER_SNAKE_CASE: ActedIn becomes ACTED_IN.

OPTIONAL MATCH

Use optionalNode<T> to produce OPTIONAL MATCH. Results may be null when no match is found:

let query = cypher {
    for p in node<Person> do
    for m in optionalNode<Movie> do
    matchRel (p -- edge<ActedIn> --> m)
    select (p, m)
}
// MATCH (p:Person) OPTIONAL MATCH (m:Movie)
// MATCH (p)-[:ACTED_IN]->(m) RETURN p, m

Variable-Length Paths

Use matchPath with a PathLength specifier for traversals:

// Friends within 1-5 hops
let query = cypher {
    for p in node<Person> do
    for q in node<Person> do
    matchPath (p -- edge<Knows> --> q) (Between(1, 5))
    select (p.Name, q.Name)
}
// MATCH (p)-[:KNOWS*1..5]->(q)

Available path lengths:

Create Relationship

Use createRel to create relationships between existing nodes:

let query = cypher {
    for p in node<Person> do
    for m in node<Movie> do
    where (p.Name = "Tom")
    createRel (p -- edge<ActedIn> --> m)
}
// MATCH (p:Person) MATCH (m:Movie)
// WHERE p.name = $p0
// CREATE (p)-[:ACTED_IN]->(m)

Incoming & Undirected

// Incoming: (p)<-[:DIRECTED]-(m)
matchRel (p -- edgeIn<Directed> --> m)

// Undirected: (p)-[:KNOWS]-(q)
matchRel (p -- edgeUn<Knows> --> q)

edge<R> = outgoing (default), edgeIn<R> = incoming, edgeUn<R> = undirected.

EXISTS Subquery

Check if a relationship exists in WHERE:

let q = cypher {
    for p in node<Person> do
    for m in node<Movie> do
    where (existsRel (p -- edge<ActedIn> --> m))
    select p
}
// WHERE EXISTS { MATCH (p)-[]->(m) }

Naming Conventions

F# Type Cypher Type Convention
ActedIn ACTED_IN UPPER_SNAKE_CASE
Knows KNOWS Single word stays uppercase
PartOf PART_OF Split on PascalCase boundaries
[<Label "FRIEND_OF">] type FriendOf FRIEND_OF Custom via attribute

See Also