Skip to main content
Version: 1.x

Record links

One of the most powerful features of SurrealDB is the ability to traverse from record-to-record without the need for traditional SQL JOINs. Each record ID points directly to a specific record in the database, without needing to run a table scan query. Record IDs can be stored within other records, allowing them to be linked together.

Creating a record

When you create a record without specifying the id, then a randomly generated id is created and used for the record id.

CREATE person SET name = 'Tobie';

person:aio58g22n3upq16hsani

It's also possible to specify a specific record id when creating or updating records.

CREATE person:tester SET name = 'Tobie';

person:tester

Records ids can be stored directly within other records, either as top-level properties, or nested within objects or arrays.

CREATE person:jaime SET name = 'Jaime', friends = [person:tobie, person:simon];
CREATE person:tobie SET name = 'Tobie', friends = [person:simon, person:marcus];
CREATE person:simon SET name = 'Simon', friends = [person:jaime, person:tobie];
CREATE person:marcus SET name = 'Marcus', friends = [person:tobie];

Fetching remote records from within records

Records ids can be stored directly within other records, either as top-level properties, or nested within objects or arrays. Nested field traversal can be used to fetch the properties from the remote records, as if the record was embedded within the record being queried.

SELECT friends.name FROM person:tobie;
[
{
friends: {
name: ["Simon", "Marcus"]
}
}
]

There is no limit to the number of remote traversals that can be performed in a query. Using . dot notation, SurrealDB does not differentiate between nested object properties, or remote records, and will fetch remote records asynchronously when needed for a query.

SELECT friends.friends.friends.name FROM person:tobie;
[
{
friends: {
friends: {
friends: {
name: [
[ ["Tobie", "Simon"], ["Simon", "Marcus"] ],
[ ["Simon", "Marcus"] ]
]
}
}
}
}
]

A DEFINE FIELD statement can be used to ensure that a field can only be a record link. A single record link is of type record<record_name>. In practice, option<array<record<record_name>>> offers the most flexibility by allowing a type to have any number of record links, including none at all.

DEFINE FIELD friends ON TABLE person TYPE option<array<record<person>>>;
CREATE person:jaime SET name = 'Jaime', friends = [person:tobie, person:simon];
CREATE person:tobie SET name = 'Tobie', friends = [person:simon, person:marcus];
CREATE person:simon SET name = 'Simon', friends = [person:jaime, person:tobie];
-- Trying to assign the wrong type to `friends` will now generate an error
CREATE person:marcus SET name = 'Marcus', friends = "Tobie!";

-- Sometimes a single non-optional `record<record_name>` does make the most sense
DEFINE FIELD counterpart ON TABLE pole TYPE record<pole>;
INSERT INTO pole [
{ id: pole:north, counterpart: pole:south },
{ id: pole:south, counterpart: pole:north },
];

Next steps

You've now seen how to create records using randomly generated ids, or specific record ids. This is just the beginning! The power and flexibility which is possible with the remote record fetching functionality within queries opens up a whole new set of possibilities for storing and querying traditional data, and connected datasets. Take a look at the next chapter to understand how futures can be used to dynamically compute values only when retrieved.

Also checkout this explainer video on using record links in SurrealDB: