Graph queries in SurrealDB use SurrealQL’s -> arrow syntax to walk relationships between records.
Basic forward and reverse traversals
SELECT ->wrote->post.* AS userPosts
FROM users:alice;FROM users:alicestarts at that node.->wrote->posts.*follows thewroteedge to posts and returns full post records asuserPosts.
Traverse in the reverse direction (for example, from a post to its authors):
SELECT <-wrote<-author AS authors
FROM post:helloworld;<-wrote<-author means “follow any wrote edge into this post from an author”.
Chaining paths
From comments, find authors and then all of their comments:
SELECT <-wrote<-user->wrote->comment FROM comment;Querying symmetric (“between equals”) relations
For edges like `friends_with`, it may be unclear whether a `person` is on the `in` or `out` side. The `` operator traverses **both** directions on that edge table:
SELECT *, <->friends_with<->person AS friends FROM person;Each row lists the other people in the relation, regardless of which side they were on:
[
{
friends: [
person:one,
person:two
],
id: person:one
},
{
friends: [
person:one,
person:two
],
id: person:two
}
]To drop the current record’s own id from the list, use array::complement():
SELECT
*,
array::complement(<->friends_with<->person, [id]) AS friends
FROM person;[
{
friends: [
person:two
],
id: person:one
},
{
friends: [
person:one
],
id: person:two
}
]For more on this pattern, see the RELATE statement and Chapter 7 of Aeon's Surreal Renaissance.
Traverse directly from a record id
Traversal can start at one or more record IDs without wrapping everything in SELECT:
CREATE ONLY user:mcuserson SET name = "User McUserson";
CREATE ONLY comment:one SET
text = "I learned something new!",
created_at = time::now();
CREATE ONLY cat:pumpkin SET name = "Pumpkin";
RELATE user:mcuserson->wrote->comment:one SET
location = "Arizona",
os = "Windows 11",
mood = "happy";
RELATE user:mcuserson->likes->cat:pumpkin;-- Equivalent to:
-- SELECT VALUE <-wrote<-user FROM ONLY comment:one;
comment:one<-wrote<-user;
-- Equivalent to:
-- SELECT VALUE ->likes->cat FROM ONLY user:mcuserson;
user:mcuserson->likes->cat;-------- Query --------
[user:mcuserson]
-------- Query --------
[cat:pumpkin]To include fields when starting from an id, use destructuring:
-- Equivalent to:
-- SELECT name, ->likes->cat AS cats FROM ONLY user:mcuserson;
user:mcuserson.{ name, cats: ->likes->cat };Automatic flattening in graph queries
When two lookups follow each other, or a filter follows a lookup, results can flatten in a way that mirrors the graph shape. This example builds a small org chart and walks up and down works_for:
CREATE
// One president
person:president,
// Two managers
person:manager1, person:manager2,
// Four employees
person:employee1, person:employee2, person:employee3, person:employee4;
// Employees work two to a manager, managers work two to a president
RELATE [person:manager1, person:manager2]->works_for->person:president;
RELATE [person:employee1, person:employee2]->works_for->person:manager1;
RELATE [person:employee3, person:employee4]->works_for->person:manager2;
[person:employee1, person:employee2, person:employee3, person:employee4]
->works_for->person
->works_for->person
<-works_for<-person
<-works_for<-person;The result is four arrays, each reflecting the up-and-down path through the president.
You can approximate the same with nested SELECT / map, but nesting grows quickly:
[person:employee1, person:employee2, person:employee3, person:employee4]
// Each $p is a single record: an array<record>
.map(|$p| SELECT VALUE out FROM works_for WHERE in = $p.id)
// Each $p is an array, so now you have to map each item inside that
.map(|$p| $p.map(|$p| SELECT VALUE out FROM works_for
WHERE in = $p.id))
// Now an array<array<array<record>>>
.map(|$p| $p.map(|$p| $p.map(|$p| SELECT VALUE in FROM works_for
WHERE out = $p.id)))
// Now an array<array<array<array<record>>>>
.map(|$p| $p.map(|$p| $p.map(|$p| $p.map(|$p| SELECT VALUE in
FROM works_for WHERE out = $p.id))));Graph syntax keeps structure aligned with the traversal. Calling flatten() at each step produces a flat list of every record seen along the way instead:
[person:employee1, person:employee2, person:employee3, person:employee4]
.map(|$p| SELECT VALUE out FROM works_for WHERE in = $p.id).flatten()
.map(|$p| SELECT VALUE out FROM works_for WHERE in = $p.id).flatten()
.map(|$p| SELECT VALUE in FROM works_for WHERE out = $p.id).flatten()
.map(|$p| SELECT VALUE in FROM works_for WHERE out = $p.id).flatten();Treat graph flattening as preserving the shape of the walk, not necessarily a single post-hoc array.
Graph paths in schema fields
Paths are not limited to SELECT; they can appear in DEFINE FIELD:
DEFINE FIELD employers
ON TABLE person VALUE SELECT VALUE <-employs<-company FROM ONLY $this;
CREATE person:1, person:2, company:1;
RELATE company:1->employs->person:1;
person:1.*;A plain VALUE field is computed when the record is created or updated; if the RELATE runs later, the field can be stale until an UPDATE:
UPDATE person:1;[
{
employers: [
company:1
],
id: person:1
}
]A computed field is recomputed on read:
DEFINE FIELD employers ON TABLE person COMPUTED <-employs<-company;
CREATE person:1, person:2, company:1;
RELATE company:1->employs->person:1;
person:1.*;{
employers: [
company:1
],
id: person:1
}Using Surrealist Explorer to debug paths
Surrealist’s Explorer view lets you step through records and relations one hop at a time, useful for building intuition for queries like SELECT ->wrote->comment FROM user.
Example setup with two outgoing edges:
CREATE user:mcuserson SET name = "User McUserson";
CREATE comment:one SET
text = "I learned something new!",
created_at = time::now();
CREATE cat:pumpkin SET name = "Pumpkin";
RELATE user:mcuserson->wrote->comment:one SET
location = "Arizona",
os = "Windows 11",
mood = "happy";
RELATE user:mcuserson->likes->cat:pumpkin;Walkthrough:
Open
user, thenuser:mcuserson.Open the Relations tab (outgoing
->).Follow
wroteinto its edge row, thencommentto the comment row, matching->wrote->comment.
Working backward in the Explorer is a good way to assemble a path while learning the syntax.
Further reading
Recursive traversals for
@.{n}and nested recursive shapes.