Developers · Data
Loading Related Database Items
Last updated 10 November 2026
This guide discusses loading related records while avoiding n+1 queries. These types of queries slow down the loading process because displaying each database item produces a request for another.
Prerequisites
This is an advanced tutorial. To follow it, you must understand basic Insites concepts, the topics in the Tables and Database Fields. For testing, you must be acquainted with measuring the execution time of Liquid code fragments using the
Steps
The sample scenario in this guide walks you through two approaches to demonstrate the difference between the naive and the recommended approach to loading related records.
Important
Use the recommended approach when implementing related records. That is, request data from a related record within a GraphQL query.
Loading related records in this demonstrative scenario is a five-step process:
- Step 1: Creating the database items
- Step 2: Loading related record items for each record (not recommended)
- Step 3: Testing the naive approach
- Step 4: Requesting data from a related record within the GraphQL query (recommended)
- Step 5: Comparing the results of the naive approach vs. the recommended approach
Step 1: Creating the database items
The data schema in this scenario consists of two records:
app/schema/company.yml
name: company
properties:
- name: name
type: string
- name: email
type: string
- name: url
type: stringapp/schema/programmer.yml
name: programmer
properties:
- name: title
type: string
- name: email
type: string
- name: company_id
type: stringThe goal is to load data from the
Programmers:
| title | company_id | |
|---|---|---|
| [email protected] | junior | 238415 |
| [email protected] | middle | 238416 |
| [email protected] | senior | 238417 |
| [email protected] | senior | 238421 |
Companies:
| id | name | url |
|---|---|---|
| 238415 | Company #2 | company-2.com |
| 238416 | Company #3 | company-3.com |
| 238417 | Company #4 | company-4.com |
| 238421 | Company #8 | company-8.com |
Result collection:
| programmer.email | programmer.title | company.name | company.url |
|---|---|---|---|
| [email protected] | junior | Company #2 | company-2.com |
| [email protected] | middle | Company #3 | company-3.com |
| [email protected] | senior | Company #4 | company-4.com |
| [email protected] | senior | Company #8 | company-8.com |
Step 2: Loading related record items for each record (not recommended)
Use two GraphQL queries for this approach:
app/graphql/programmers.graphql
query programmers {
programmers: records(
per_page: 20,
filter: {
table: { value: "programmer" }
}
) {
total_entries
results {
properties
}
}
}app/graphql/company.graphql
query company($id: ID) {
companies: records(
per_page: 20,
filter: {
table: { value: "company" },
id: { value: $id }
}
) {
results {
table
id
deleted_at
properties
}
}
}While displaying the programmer collection — for each programmer record — fetch the related company record using the
{% graphql g = "programmers" %}
{% for programmer in g.programmers.results %}
<tr>
<td>{{ programmer.properties.email }}</td>
<td>{{ programmer.properties.title }}</td>
{% if programmer.properties.company_id %}
{% graphql company = "company", id: programmer.properties.company_id %}
<td>{{ company.companies.results.first.properties.name }}</td>
<td>{{ company.companies.results.first.properties.url }}</td>
{% endif %}
</tr>
{% endfor %}Step 3: Testing the naive approach
Use the time_diff Liquid filter to test the naive approach. The quantitative results are displayed in the table below:
| overall | |
|---|---|
| benchmark-n+1 | 1,236.169 |
| 1,223.485 | |
| 1,418.733 | |
| 1,216.547 | |
| 1,314.053 |
Note
Please visit the documentation about Measuring the Execution Time of Liquid Code to learn how to set up tests for measuring code runtime.
Step 4: Request related data in the GraphQL query (recommended)
To avoid n+1 queries, request company data within the
related_record(table: "company", join_on_property: "company_id") { properties }The
SELECT * FROM programmers JOINS companies ON (programmers.company_id = companies.id)Your GraphQL query would look like this after updating:
query programmers {
programmers: records(
filter: {
table: { value: "programmer" }
},
per_page: 200
) {
total_entries
results {
properties
company: related_record(table: "company", join_on_property: "company_id") {
url: property(name: "url")
properties
}
}
}
}Update the
app/views/pages/programmers/index.liquid
{% for programmer in g.programmers.results %}
<tr>
<td> {{ programmer.properties.email }} </td>
<td> {{ programmer.properties.title }} </td>
<td>{{ programmer.company.properties.name }} </td>
<td>{{ programmer.company.properties.url }} </td>
</tr>
{% endfor %}Step 5: Comparing the results of the naive approach vs. the recommended approach
Test the second approach using the same measurement method. The results are as follows:
| overall | |
|---|---|
| benchmark-record | 122.504 |
| 160.885 | |
| 134.064 | |
| 160.051 | |
| 131.056 |
Observe that requesting data from the related record within the GraphQL query delivers results around ten times faster than the previous method outlined in Step 3. The n+1 queries in the naive approach slow down the process because displaying each database item produces a request for another database item.