Record
Record
Level 1 — What Is SurrealDB? The fundamental unit of data storage in SurrealDB, serving as the direct equivalent to a row in PostgreSQL or a document in MongoDB, but uniquely identified by a composite
table:idRecord ID.
1. Prerequisites
- SurrealQL — The query language used to fetch records.
2. Term Category
Core Concept (document-like data record unit): - Database Structure / Paradigm
3. Explanation
(1) Design Motivation — "Why did we design this?"
In relational databases, data is split across tables using flat Rows:
- A row cannot hold nested arrays or sub-objects natively without violating database design rules (First Normal Form).
- To store a user's address list, you must create a separate table and link it with foreign keys.
In document databases, data is stored as nested Documents:
- A document can hold arrays easily, but connecting it to other documents requires manual object references that are hard to manage.
We designed the Record in SurrealDB to combine both paradigms.
A record stores data as a hierarchical JSON-like object, allowing you to embed arrays and objects natively (like MongoDB).
At the same time, every record acts as a node in a relational graph, uniquely identified by a composite table:id label (like user:john), allowing direct pointers and relationships without the complexity of traditional SQL tables.
(2) Record Vocabulary Across Databases
How records compare across the three systems:
| Concept | PostgreSQL | MongoDB | SurrealDB |
|---|---|---|---|
| Primary Unit | Row | Document | Record |
| Storage Container | Table | Collection | Table |
| Identifier | Primary Key (separate column) | _id field (ObjectId) | Record ID (fused table:id) |
| Structure | Flat columns | Nested BSON | Nested JSON (Objects/Arrays) |
(3) Reality Metaphor (The Smart Folder)
Imagine keeping office records:
- PostgreSQL Row: A line on a Pre-Printed Ledger Sheet.
- You must write values strictly inside the vertical column grids.
- You cannot write paragraphs or attach sub-lists.
- MongoDB Document: A Blank Binder Sheet.
- You can write anywhere, glue receipts, or draw lists.
- However, if a sheet refers to another binder, you must write the ID down and search for it manually in the archive cabinets.
- SurrealDB Record: A Smart Manila Folder.
- It stores nested sheets (arrays and objects), has a fused label on its tab (
patient:tobie), and contains a physical string tag pointing directly to another folder (doctor: doctor:smith). - Touching the tag pulls the doctor's folder directly to your desk.
- It stores nested sheets (arrays and objects), has a fused label on its tab (
(4) Code Examples
Structure of a SurrealDB Record
This is how a typical user record is written and stored in SurrealDB:
// A single User Record (JSON representation)
{
id: user:tobie, // The fused Record ID (table:id)
name: { // Nested Object
first: "Tobie",
last: "Morgan"
},
email: "tobie@surrealdb.com", // String field
roles: ["admin", "developer"], // Array field
registered_at: d"2026-07-21T15:30:00Z" // Datetime type
}
4. Common Mistakes & Pitfalls
Mistake 1: Normalizing every array field into separate tables because you assume SurrealDB records must be flat like SQL rows
The mistake: Creating a separate user_emails table to store multiple emails for a user, rather than storing them as a string array inside the user record.
Why it's wrong: SurrealDB records support nested JSON objects and arrays natively.
Forcing relational normalization on simple nested lists adds query overhead and slows down development, as you must write complex queries to fetch basic data.
Fix: Embed list values (like tags, configurations, or addresses) directly inside arrays or objects within the record document. Only separate data into different tables if the nested items represent independent entities that need their own unique IDs (like products in an order).
Mistake 2: Treating Record IDs as Simple Integer Auto-Increment IDs
The mistake: Expecting record IDs to be sequential integers like 1, 2, 3.
Why it's wrong: SurrealDB Record IDs are structured string-table pairs (table:id, e.g., user:alice or user:ulid()).
Incorrect:
-- Assuming integer ID primary keys
SELECT * FROM user WHERE id = 1; // ❌ Fails to match record user:1 or user:alice
Fix:
SELECT * FROM user:1; // Direct primary key record lookup by Record ID
Mistake 3: Creating Duplicate Record IDs with CREATE Statement
The mistake: Running CREATE user:alice CONTENT { name: "Alice" }; twice.
Why it's wrong: CREATE fails and throws an error if the specified Record ID already exists! Use UPSERT or UPDATE if overwriting or updating existing records is intended.
Incorrect:
-- If user:alice already exists:
CREATE user:alice CONTENT { name: "Alice" }; // ❌ Error: Record user:alice already exists!
Fix:
UPSERT user:alice CONTENT { name: "Alice" }; // Safely creates or updates record
5. Practice Exercises
Exercise 1: Multi-Model Record Construction
Scenario: You are modeling a user profile record in SurrealDB that demonstrates its multi-model capabilities: tabular scalar fields, a nested document object, an array of tags, and a record link pointer to another table.
Requirements:
- Target table
userwith IDuser:john. - Include scalar fields
nameandemail. - Include a nested document object
settingscontainingthemeandnotifications. - Include a record link field
rolepointing torole:admin.
Answer
Implementation
CREATE user:john CONTENT {
name: "John Doe",
email: "john@example.com",
role: role:admin,
tags: ["developer", "administrator"],
settings: {
theme: "dark",
notifications: true
}
};
Technical Explanation
- SurrealDB records are JSON-like documents containing scalar values, nested objects, arrays, and record links.
role:role:adminstores a direct typed record link pointer rather than a raw foreign key string.- Nested document fields (
settings.theme) can be queried directly using dot-notation without unnesting.
Exercise 2: Record Mutation Strategies (MERGE vs CONTENT)
Scenario:
You need to update user:john's email address without overwriting or erasing the existing settings object or tags array.
Requirements:
- Write the SurrealQL statement using
MERGEorSETto perform a non-destructive partial update. - Explain why using
CONTENTfor partial updates is dangerous.
Answer
Implementation
-- Non-destructive partial update
UPDATE user:john MERGE {
email: "john.updated@example.com"
};
Technical Explanation
MERGEperforms a shallow merge, updating specified fields while preserving existing document properties.- Using
CONTENTreplaces the entire record payload, accidentally deleting any fields omitted from the payload. UPDATE ... SET email = ...is also safe for single-field mutations.
Exercise 3: Record Identifier Access Patterns
Scenario:
A backend service needs to fetch a single user record by its primary key user:john with maximum efficiency.
Requirements:
- Write the direct record selection query targeting
user:john. - Contrast the performance of direct record selection vs a filtered table scan (
SELECT * FROM user WHERE id = user:john).
Answer
Implementation
-- Direct primary key lookup (O(1) index jump)
SELECT * FROM user:john;
Technical Explanation
SELECT * FROM table:idperforms a direct primary key index lookup in constant time.- Avoid using
WHERE id = ...filters, asuser:johnacts directly as the record pointer address in SurrealDB. - Direct record targeting bypasses table scanning algorithms completely.
6. Related Terms
- Table — The parent records collection.
- Record ID (
table:id) — The unique identifier. - SurrealQL — Related concept: SurrealQL.
7. Key Takeaways
- The Record is the fundamental unit of data storage in SurrealDB.
- Directly equivalent to a PostgreSQL row or a MongoDB document.
- Stored as a hierarchical JSON-like object, supporting nested arrays and structures.
- Uniquely identified by a fused
table:idRecord ID. - Supports pointer references (record links) to other records natively.
- Eliminates the flat structure limitations of traditional SQL database rows.
- Can be schema-full (strict validation) or schema-less (fully flexible) per table.