Skip to content

Commit

Permalink
Proofread
Browse files Browse the repository at this point in the history
  • Loading branch information
Dhghomon committed Dec 31, 2024
1 parent fbe9399 commit a5374eb
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 81 deletions.
197 changes: 116 additions & 81 deletions src/content/doc-surrealdb/reference-guide/graph_relations.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ description: This guide outlines when and when not to use graph relations, and s

# Graph relations

In SurrealDB, one record can be linked to another via a table that stands in between the two that has its own name and properties. This page teaches how to determine whether this is the right way to link records in your project, and best practices for doing so.
In SurrealDB, one record can be linked to another via a graph edge, namely a table that stands in between the two that has its own ID and properties. This page teaches how to determine whether this is the ideal way to link records in your project, and best practices for doing so.

## When to use graph relations

The first item to take into account when using graph relations is whether they are the right solution in the first place, as graph edges are not the only way to link one record to another in SurrealDB.
The first item to take into account when using graph relations is whether they are the right solution in the first place, because graph edges are not the only way to link one record to another.

SurrealDB has two main ways to create relations between one record and another: record links, and graph relations.

A record link is a simple pointer from one record to another, a link that exists any time a record holds the record ID of another record. Record links are the most efficient method because record IDs are direct pointers to the data of a record, and do not require a scan of the whole table.
A record link is a simple pointer from one record to another, a link that exists any time a record holds the record ID of another record. Record links are the most efficient method because record IDs are direct pointers to the data of a record, and do not require a table scan.

Take the following example that creates one `user` who has written two `comment`s.

Expand Down Expand Up @@ -97,7 +97,7 @@ FROM comment;
]
```

The other limitation is that there is no metadata about the context in which the comment was created. Take the following metadata for instance which contains information about a user's current location, operating system, and mood.
The other limitation is that there is no metadata about the context in which the comment was created. Take the following metadata for instance which contains information about a user's current location, operating system, and mood. Where does this data belong?

```surql
{
Expand All @@ -111,7 +111,7 @@ This metadata isn't information about the user as a whole, nor the comment itsel

## Creating a graph relation

The following example is similar to the one above, except that the `user` record does not have a `comments` field, leaving it seemingly separate from the `comment` created on the next line. Instead, this time a `RELATE` statement is used to create a graph edge called `wrote` joining the two of them, and this is the table that holds the metadata mentioned above.
The following example is similar to the one above, except that this time the `user` record does not have a `comments` field, leaving it seemingly separate from the `comment` created on the next line. Instead, this time a `RELATE` statement is used to create a graph edge called `wrote` joining the two of them, and this is the table that holds the metadata mentioned above.

```surql
LET $new_user = CREATE ONLY user SET name = "User McUserson";
Expand All @@ -137,7 +137,7 @@ SELECT <-wrote<-user FROM comment;
SELECT <-wrote<-user->wrote->comment FROM comment;
```

## More on querying graph relations
## Other sources on querying graph relations

The arrow operator used to traverse graph edges is an intuitive way to visualize the direction(s) in which a query is traversing. As this page is devoted to an overview of when and how best to use graph relations, it does not go into the details of queries themselves. Many reference pages already exist in the SurrealDB documentation to learn this, including:

Expand All @@ -148,6 +148,8 @@ The arrow operator used to traverse graph edges is an intuitive way to visualize

## Tips and best practices with graph relations

The following sections detail some tips and best practices when using graph relations.

### Define a table as a relation for better type safety and visual output

Defining a table as `TYPE RELATION` ensures that it can only be created in the context of a relation between two records.
Expand All @@ -164,7 +166,7 @@ Specifying the record types at the `in` and `out` fields of a graph table will e
DEFINE TABLE likes TYPE RELATION IN person OUT blog_post | book;
```

One other advantage to strictly defining a relation table is that this information can be picked up by Surrealist to be displayed in its Designer view.
One other advantage to strictly defining a relation table is that this information can be picked up by [Surrealist](/docs/surrealist) to be displayed in its Designer view.

Take the following queries that create some records and relate them to each other.

Expand Down Expand Up @@ -203,9 +205,9 @@ RELATE person:one->likes->blog_post:one;

![alt text](schema3.png)

### Create a unique index if the graph relationship is between equals
### Create a unique index if the graph relation is between equals

While most examples involve a clear subject and object relationship, sometimes a graph edge represents a relationship such as friendship, a partnership, sister cities, etc. in which this is not clear.
While most examples involve a clear subject and object relation, sometimes a graph edge represents a relation such as friendship, a partnership, sister cities, etc. in which this is not clear.

```surql
CREATE person:one, person:two;
Expand All @@ -216,10 +218,10 @@ RELATE person:one->friends_with->person:two;
RELATE person:two->friends_with->person:one;
```

To ensure that this relationship cannot be established more than once, define a field made of the sorted `in` and `out` fields of the graph table, and define an index on it with a unique constraint.
To ensure that this relation cannot be established more than once, define a field made of the sorted `in` and `out` fields of the graph table, and define an index on it with a unique constraint.

```surql
DEFINE FIELD key ON TABLE friends_with VALUE array::sort([in, out]);
DEFINE FIELD key ON TABLE friends_with VALUE <string>array::sort([in, out]);
DEFINE INDEX only_one_friendship ON TABLE friends_with FIELDS key UNIQUE;
```

Expand All @@ -231,21 +233,82 @@ RELATE person:one->friends_with->person:two;
RELATE person:two->friends_with->person:one;
```

```surql title="Output"
'Database index `only_one_friendship` already contains person:one, with record `friends_with:hdt5hoamvs8l65cw7a3t`'
```surql title="Output of RELATE statements"
-------- Query --------
[
{
id: friends_with:dblidwpc44qqz5bvioiu,
in: person:one,
key: '[person:one, person:two]',
out: person:two
}
]
-------- Query --------
"Database index `only_one_friendship` already contains '[person:one, person:two]', with record `friends_with:dblidwpc44qqz5bvioiu`"
```

The `<->` operator can be used in this case to traverse both the `in` and `out` fields of the `friends_with` table, along with the [`array::complement()`](/docs/surrealql/functions/database/array#arraycomplement) function to exclude the record in question from showing up as its own friend.
### Querying a graph relation between equals

In a relation between equals like in the example above, it is never certain whether a specific `person` is friends with another due to a `RELATE` statement where it is the subject of the statement, or the object of the statement.

The `<->` operator can be used in this case to traverse both the `in` and `out` fields of the `friends_with` table.

```surql
SELECT *, <->friends_with<->person AS friends FROM person;
```

This will now show each of the records involved in the relation, regardless of whether they are located at the `in` or `out` field of the `friends_with` graph table.

```surql
[
{
friends: [
person:one,
person:two
],
id: person:one
},
{
friends: [
person:one,
person:two
],
id: person:two
}
]
```

To complete this query to ensure that a record's own ID does not show up inside the list of `friends`, the [`array::complement()`](/docs/surrealql/functions/database/array#arraycomplement) function can be used.

```surql
SELECT *, array::complement(<->friends_with<->person, [id]) AS friends FROM person;
```

```surql title="Output"
[
{
friends: [
person:two
],
id: person:one
},
{
friends: [
person:one
],
id: person:two
}
]
```

For further details on this pattern, see [this section](/docs/surrealql/statements/relate#bidirectional-relation-querying) in the page on the `RELATE` statement and [this section](/learn/book/chapter-07#bidirectional-querying-when-a-relationship-is-equal) of Chapter 7 of Aeon's Surreal Renaissance.

### Traverse directly from a record instead of using SELECT

As graph traversal takes place between records, the same syntax can be used directly from one or more record IDs without needing to use a `SELECT` statement.
As graph traversal takes place between records, the same syntax can be used directly from one or more record IDs without needing to use a `SELECT` statement. Take the following setup that once again creates a record linked to a comment:

```surql
CREATE ONLY user:mcuserson SET name = "User McUserson";
Expand All @@ -262,7 +325,7 @@ RELATE user:mcuserson->wrote->comment:one SET
RELATE user:mcuserson->likes->cat:pumpkin;
```

These graph edges can now be traversed simply using the record name and the arrow syntax.
These graph edges can be traversed simply using the record name and the arrow syntax.

```surql
-- Equivalent to:
Expand All @@ -288,7 +351,7 @@ user:mcuserson->likes->cat;
]
```

When including various fields in a query, the destructuring operator can be used in this case.
To include various fields in a query that begins from a record ID, the destructuring operator can be used.

```surql
-- Equivalent to:
Expand All @@ -301,14 +364,14 @@ user:mcuserson.{ name, cats: ->likes->cat };
While most examples in the documentation show how to traverse graph paths inside a `SELECT` statement, they can just as easily be defined as a field on a table.

```surql
DEFINE FIELD is_employed ON TABLE person VALUE !!(SELECT VALUE <-employs<-company FROM ONLY $this);
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.*;
```

However, note that the output of the query above shows `false` for the `is_employed` field, as it was calculated at the point that `person:1` was created. The `VALUE` clause will only recalculate if a record is updated.
However, note that the output of the query above shows an empty array for the `employers` field, as it was calculated at the point that `person:1` was created, not when the `RELATE` statement was executed. The `VALUE` clause will only recalculate if a record is updated.

```surql
UPDATE person:1;
Expand All @@ -317,35 +380,38 @@ UPDATE person:1;
```surql title="Output"
[
{
id: person:1,
is_employed: true
employers: [
company:1
],
id: person:1
}
]
```

A `future` makes more sense in this case, as a future is calculated each time a record is queried, not just whenever it is created or updated.
A [`future`](/docs/surrealql/datamodel/futures) makes more sense in this case, as a future is calculated each time a record is queried, not just whenever it is created or updated.

```surql
DEFINE FIELD is_employed ON TABLE person VALUE <future> { RETURN !!(SELECT VALUE <-employs<-company FROM ONLY $this) };
DEFINE FIELD employers ON TABLE person VALUE <future> { RETURN SELECT VALUE <-employs<-company FROM ONLY $this };
CREATE person:1, person:2, company:1;
RELATE company:1->employs->person:1;
person:1.*;
```

```surql title="Output"
{
id: person:1,
is_employed: true
employers: [
company:1
],
id: person:1
}
```

### Using Surrealist to understand graph edges

Surrealist has an Explorer view that allows users to peek into not just the records and their fields, but also traverse their relations one step at a time. This can be an effective tool to understand the internals of graph edges and queries on them.
Surrealist has an [Explorer view](/docs/surrealist/concepts/explore-database-records) that allows users to not just view records and their fields, but also traverse their relations one step at a time. This can be an effective tool to understand the internals of graph edges and queries on them.

Take the following example similar to the one above, except that the `user` this time has two graph relations instead of one.
Take the following example similar to the ones above, except that the `user` this time has two graph relations instead of one.

```surql
CREATE user:mcuserson SET name = "User McUserson";
Expand All @@ -364,7 +430,7 @@ RELATE user:mcuserson->likes->cat:pumpkin;

The Explorer view inside Surrealist can then be used to understand a query like `SELECT ->wrote->comment FROM user` and what the database sees at each and every step of the way.

* Click on `user` (this is the `FROM user` part), then the individual `user:mcuserson` recard.
* Click on `user` (this is the `FROM user` part), then the individual `user:mcuserson` record.
* Click on the `Relations` tab. This has two outgoing relations, outgoing being the `->` direction.
* The path in the query above then goes into `wrote`, so click on that to move into the single `wrote` record.
* At its Outgoing relations is a `comment`, which matches the `->comment` part of the path.
Expand All @@ -374,13 +440,14 @@ Reversing the process by beginning with the Explorer view is a good way to build

### RELATE can be used before records to relate exist

One record can be related to another before the two records exist.
One characteristic of graph tables is that they can be created before the two records in question exist.

```surql
-- Works fine
RELATE person:one->likes->person:two;
-- Returns []
person:one->likes->person;
-- Finally create the 'person' records
CREATE person:one, person:two;
-- Now it returns [ person:two ]
person:one->likes->person;
Expand All @@ -396,40 +463,36 @@ DEFINE TABLE likes TYPE RELATION IN person OUT person ENFORCED;
"The record 'person:one' does not exist"
```

However, certain patterns might make it desirable to use `RELATE` before creating a record. If a record has a field with a `VALUE` clause that depends on a graph edge, it will only be calculated when the record is created or updated. In this case, a final `CREATE` for the records involved will allow the value to be calculated once, instead of needing to manually `UPDATE` the records once more after they are created.

```surql
-- A star has planets
DEFINE FIELD planets ON TABLE star VALUE $this<-orbits<-planet;
-- A planet orbits a start and has moons
DEFINE FIELD orbits ON TABLE planet VALUE $this.id->orbits->star;
DEFINE FIELD moons ON TABLE planet VALUE $this.id<-orbits<-moon;
-- A moon orbits a planet
DEFINE FIELD orbits ON TABLE moon VALUE $this.id->orbits->planet;
RELATE [planet:mercury, planet:venus, planet:earth, planet:mars]->orbits->star:the_sun;
RELATE moon:the_moon->orbits->planet:earth;
RELATE [moon:phobos, moon:deimos]->orbits->planet:mars;
CREATE star:the_sun, planet:mercury, planet:venus, planet:earth, planet:mars, moon:phobos, moon:deimos;
SELECT * FROM planet;
```

Alternatively, a situation might simply involve a relation that exists when a record does not. For example, a street in a city might have a set of addresses registered with a predictable record ID (such as an ID composed of a street number and name) but no houses at the location yet.
However, certain patterns might make it desirable to use `RELATE` before creating a record. For example, a street in a city might have a set of addresses registered with a predictable record ID (such as an ID composed of a street number and name) but no houses at the location yet. A `DEFINE FIELD` statement can be used here that contains the path from the `house` to the `street` that will be calculated once the `house` is finally created.

```surql
DEFINE FIELD street ON house VALUE $this<-contains<-street;
CREATE street:frankfurt_road;
RELATE street:frankfurt_road->contains->[
house:[200, "Frankfurt Road"],
house:[205, "Frankfurt Road"],
house:[210, "Frankfurt Road"],
];
-- Twelve months later...
-- Twelve months later once the house is built and size is known...
CREATE house:[200, "Frankfurt Road"] SET sq_m = 110.5;
```

```surql title="Output"
[
{
id: house:[
200,
'Frankfurt Road'
],
sq_m: 110.5f,
street: [
street:frankfurt_road
]
}
]
```

### Using recursive queries

[Recursive queries](/docs/surrealql/datamodel/idioms#recursive-paths) allow traversal of a path down to a specific depth.
Expand Down Expand Up @@ -493,32 +556,4 @@ While developed for graph relations in particular, this path can be used in any
For more details on SurrealDB's recursive syntax, see the following pages:

* [Idioms: recursive paths](/docs/surrealql/datamodel/idioms#recursive-paths)
* [Chapter 8 of Aeon's Surreal Renaissance](/learn/book/chapter-08#longer-relational-queries)

### Don't use them if you can use a record link instead

To sum up the post with the point made at the beginning, graph edges are just one of two ways to join records and are not always the ideal solution. If graph edges are not strictly necessary and performance is key, then record links are the way to go.

The following example that links one `person` to 10000 `cat` records in both ways shows at the end how much more performant record links are.

```surql
LET $cats = CREATE |cat:1..10000|;
CREATE person:1 SET cats = $cats;
FOR $cat IN $cats {
RELATE person:1->owner_of->$cat;
};
-- Record links, very performant
LET $_ = SELECT cats FROM person:1;
-- Graph traversal, more complex query
LET $_ = person:1->owner_of->cat;
```

This can be seen visually by adding the `owner_of` table and `cats` field to the schema inside Surrealist. As the Designer view shows, the `cats` field is simply a direct link to a `cat` record, while to reach `cat` via graph traversal will always involve the `owner_of` table located in between the two.

```surql
DEFINE TABLE OVERWRITE owner_of TYPE RELATION IN person OUT cat;
DEFINE FIELD cats ON TABLE person TYPE array<record<cat>>;
```

![alt text](schema4.png)
* [Chapter 8 of Aeon's Surreal Renaissance](/learn/book/chapter-08#longer-relational-queries)
Binary file not shown.

0 comments on commit a5374eb

Please sign in to comment.