Use SurrealQL arrow syntax, bidirectional edges, traversals from record IDs, automatic flattening, graph paths in schema fields, and Surrealist’s Explorer to debug paths step by step.
Graph queries in SurrealDB use SurrealQL’s -> arrow syntax to walk relationships between records.
<-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->commentFROMcomment;
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:
-- 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;
Output
-------- 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;
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|SELECTVALUEoutFROMworks_forWHEREin=$p.id) // Each $p is an array, so now you have to map each item inside that .map(|$p|$p.map(|$p|SELECTVALUEoutFROMworks_forWHEREin=$p.id)) // Now an array<array<array<record>>> .map(|$p|$p.map(|$p|$p.map(|$p|SELECTVALUEinFROMworks_forWHEREout=$p.id))) // Now an array<array<array<array<record>>>> .map(|$p|$p.map(|$p|$p.map(|$p|$p.map(|$p|SELECTVALUEinFROMworks_forWHEREout=$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:
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.