Choose between record links and graph edges for performance, schema, bidirectional references, metadata on relationships, weighting, and delete behaviour.
The first question when modeling connections is whether graph edges are the right tool: SurrealDB can also connect records with record links.
Record links
A record link is a pointer from one record to another: any field that holds a record id. Record ids are efficient direct pointers and avoid table scans.
Example: one user with two comment rows linked from the user:
LET$new_user = CREATEONLYuserSETname="User McUserson"; -- Create a new comment, use the output to update the user UPDATE$new_userSETcomments+= (CREATEONLYcommentSET text="I learned something new!", created_at=time::now()) .id; UPDATE$new_userSETcomments+= (CREATEONLYcommentSET text="I don't get it, can you explain?", created_at=time::now()) .id;
[ { comments: [ { created_at: d'2024-12-12T02:39:07.644Z', text: 'I learned something new!' }, { created_at: d'2024-12-12T02:39:07.645Z', text: "I don't get it, can you explain?" } ], name: 'User McUserson' } ]
Reverse direction without edges
Historically, record links were unidirectional; the other direction often needed a subquery; while graph edges made reverse walks easy:
SELECT *, -- Check the `user` table's `comments` field -- for the id of the current comment (SELECTid, nameFROMuserWHERE$parent.idINcomments) ASauthor FROMcomment;
-- Equivalent graph query is much easier -- to read and write SELECT *, <-wrote<-author FROMcomment;
LET$new_user = CREATEONLYuserSETname="User McUserson"; -- Create a new comment, use the output to update the user UPDATE$new_userSETcomments+= (CREATEONLYcomment:oneSET text="I learned something new!", created_at=time::now()) .id; UPDATE$new_userSETcomments+= (CREATEONLYcomment:twoSET text="I don't get it, can you explain?", created_at=time::now()) .id;
-- 'author' field is populated with the 'user' who wrote the comment SELECT * FROMONLYcomment:one;
-- Regular queries on incoming references work too SELECTtext, <~user.id[0]AScommenterFROMcomment;
[ { commenter: user:igi77zrlewsqemgjz4zi, text: 'I learned something new!' }, { commenter: user:igi77zrlewsqemgjz4zi, text: "I don't get it, can you explain?" } ]
When to prefer record links
Record links are preferred if:
Performance is the top priority.
You do not need very complex multi-hop graph queries.
You want the schema to declare what happens when a linked row is deleted (cascade, refuse, ignore, etc.) via ON DELETE and related options.
When to prefer graph relations
Graph relations are preferred if:
You want to create links quickly without pre-defining every field, or you link across many record types in one go (for example RELATE person:one->wrote->[blog:one, book:one, comment:one] versus several DEFINE FIELD steps.
You need expressive arrow syntax for multi-hop patterns such as ->wrote->comment<-wrote<-person->wrote->comment starting from person.
You want Surrealist’s Designer to visualize edges clearly.
Graph edges are almost required when the relationship itself carries metadata that belongs neither to the source nor target record alone (for example the moment a user posted a comment:
Graph tables are a natural place to store weights on the edge row, or to derive interaction strength by counting how many edges exist between the same endpoints. If you need that kind of scoring or event metadata on the relationship itself, graph relations are usually the right abstraction.
Worked examples (random NPC interactions, knows vs greeted, aggregations) live in Social network patterns.
When links are deleted
Record links can encode rich behaviour on delete (for example, removing an id from an array while appending to a history field), via REFERENCEON DELETE.
Graph edges are simpler: if either endpoint record is deleted, the edge row is removed.
-- likes record created without problems RELATEperson:one->likes->person:two; CREATEperson:one, person:two; DELETEperson:one; -- 'likes' record is now gone SELECT * FROMlikes;