MongoDB, CouchDB, and DynamoDB, all of which are in the broad category of document stores (although with varying specific features).
But how do you “think” in a document model database? Thinking in a document model means orienting your data design around the entities as you naturally represent them in your applications, rather than forcing your data into normalized or heavily structured relational schemas.
For example, you have a table called users
in your application, the document model database will store user data (e.g., name, age, addresses) in a single JSON-like document.
By clicking the Run query button, you will see the following result:
[ { "id": "users:a2ndbh1hsquvkvthws09", "name": "Alice Smith", "email": "alice@example.com", "age": 29, "addresses": [ { "type": "home", "address_line": "123 Maple St", "city": "Springfield", "country": "USA" }, { "type": "work", "address_line": "456 Oak Ave", "city": "Metropolis", "country": "USA" } ] } ]
You may notice that the id
field has a users:
prefix. This is because SurrealDB uses a id to uniquely identify each record and the combination of the table name and the record id is used as the record link.
In this guide, we will explore how to think in a document model database, in SurrealDB. We will also explore CRUD statements and how to query data in SurrealDB.
Document model databases are designed to store data in a flexible, nested structure. They are often used for applications that require high performance, scalability, and flexibility. A
Data Organization: Data is contained in self-describing documents, typically in JSON or a similar format. Relationships, can be done within the document itself (embedding) or references using record links to other documents.
Schema Flexibility: Schemas are often flexible, allowing for documents of varying shapes in the same collection (or table-like structure).
For example, if you wanted to associate a person
with an article
they wrote, you could do this by assigning the person’s ID to the author
field of the article document. This binds the person
and article
together, allowing you to query the article
by the person
’s ID.
When thinking in a document model database, you will often find that the concepts are similar to those in SurrealDB. Below is a mapping of the most common concepts between a document model database and SurrealDB.
Document Model | SurrealDB | ||
---|---|---|---|
database | database | ||
collection | table | ||
document | record | ||
field | field | ||
index | index | ||
Objectid | record id | ||
transactions | transactions | ||
reference and embedding | record links, embedding and graph relations |
Flexibility: You don’t need to define rigid schemas in advance. Changes to data structure are often just changes in the JSON object itself.
Natural Data Representation: Since you’re working with JSON-like objects, document databases align well with modern programming languages that manipulate data as objects or dictionaries.
Simplicity of Application Code: Because you can embed everything related to an entity in a single document, you often have fewer JOINs (or complex queries) and simpler code for retrieving complete objects.
Easier Horizontal Scaling: Many document databases are built for horizontal partitioning (sharding), making them easier to scale for large workloads.
In SurrealDB, you can create a database and then store collections of documents (often referred to as “tables”) without strict schema definitions. Let’s walk through an example.
This is conceptually similar to creating a table in a relational database, but in SurrealDB you don’t necessarily need to define all columns upfront. Instead, you can just insert JSON-like objects.
CREATE users CONTENT { name: "Alice Smith", email: "alice@example.com", age: 29, addresses: [ { type: "home", address_line: "123 Maple St", city: "Springfield", country: "USA" }, { type: "work", address_line: "456 Oak Ave", city: "Metropolis", country: "USA" } ] };
Here, we’ve created a user document with nested objects (addresses
) in the same record. Notice how flexible this is: we can add as many nested objects or properties as we want, without having to modify any schema.
SELECT * FROM users;
The above query returns all documents in the users
table, much like a traditional SQL SELECT
statement. Because SurrealDB is multi-model, the returned data might look like:
[ { "id": "users:xyz123", "name": "Alice Smith", "email": "alice@example.com", "age": 29, "addresses": [ { "type": "home", "address_line": "123 Maple St", "city": "Springfield", "country": "USA" }, { "type": "work", "address_line": "456 Oak Ave", "city": "Metropolis", "country": "USA" } ] } ]
SurrealDB automatically generates a unique id
for the document. You can also specify your own custom IDs if you prefer more human-readable or domain-specific identifiers.
As a multi-model database, SurrealDB offers a lot of flexibility. Our SQL-like query language SurrealQL is a good example of this, where we often have more than one way to achieve the same result, depending on developer preference. In this mapping guide, we will focus on the syntax that most closely resembles the MongoDB query language (MQL).
Let’s get you up to speed with SurrealQL syntax with some CRUD examples.
As MongoDB is schemaless, only the SurrealQL schemaless approach is shown below. For a schemafull option see the DEFINE TABLE
page.
For more SurrealQL examples, see the CREATE
and INSERT
pages.
MQL | SurrealQL | ||
---|---|---|---|
db.createCollection(“person”) | CREATE person | ||
db.person.insertMany([{ name: “John” }, { name: “Jane” }]) | INSERT INTO person [ {name: “John”}, {name: “Jane”} ] | ||
db.person.createIndex({ name: 1 }) | DEFINE INDEX idx_name ON TABLE person COLUMNS name |
For more SurrealQL examples, see the SELECT
, LIVE SELECT
and RETURN
pages.
MQL | SurrealQL | ||
---|---|---|---|
db.person.find() | SELECT * FROM person | ||
db.person.find({}, { _id: 0, name: 1 }) | SELECT name FROM person | ||
db.person.find({ name: “Jane” }, { _id: 0, name: 1 }) | SELECT name FROM person WHERE name = “Jane” | ||
db.person.find({ name: “Jane” }, { _id: 0, name: 1 }).explain() | SELECT name FROM person WHERE name = “Jane” EXPLAIN | ||
db.person.aggregate([{ $count: “personCount” }]) | SELECT count() AS person_count FROM person GROUP ALL | ||
db.person.aggregate([{ $group: { _id: “$name” } }]) | SELECT array::distinct(name) FROM person GROUP ALL | ||
db.person.find().limit(10) | SELECT * FROM person LIMIT 10 | ||
db.review.aggregate([{ “$lookup”: { “from”: “person”, “localField”: “person”, “foreignField”: “_id”, “as”: “person_detail” } }]) | SELECT *, person.name as reviewer FROM review |
For more SurrealQL examples, see the UPDATE
page.
MQL | SurrealQL | ||
---|---|---|---|
db.person.updateMany({ name: “Jane” }, { $set: { last_name: “Doe” } }) | UPDATE person SET last_name = “Doe” WHERE name = “Jane” | ||
db.person.updateMany({ name: “Jane” }, { $unset: { last_name: 1 } }) | UPDATE person UNSET last_name WHERE name = “Jane” |
For more SurrealQL examples, see the DELETE
and REMOVE
pages.
MQL | SurrealQL | ||
---|---|---|---|
db.person.deleteMany({ name: “Jane” }) | DELETE person WHERE name = “Jane” | ||
db.person.deleteMany({}) | DELETE person | ||
db.person.drop() | REMOVE TABLE person |
CREATE
statementSELECT
statementUPDATE
statementDELETE
statementRELATE
statement