Modeling data - embed or reference
Azure Cosmos DB for NoSQL stores JSON items, and there are no server-side joins between containers. The model you choose decides how many requests and request units (RUs) each read costs, so modeling is the most tested design skill.
Think in access patterns
Do not start from entities and tables. Start from the list of queries and writes the app runs most often, then shape items so the common reads are answered by one point read or one single-partition query. A point read (ReadItemAsync with id and partition key) costs about 1 RU for a 1 KB item, which is cheaper than any query.
Embed or reference
| Choose | When | Result |
|---|---|---|
| Embed (nested object or array) | Data is read together, changes rarely, and is bounded in size | One read, atomic update of the item |
| Reference (store the id of another item) | Data is unbounded, changes often, or is used by many parents | Smaller items, but you need a second read |
An item can be at most 2 MB, so never embed an array that grows without limit. Many-to-many relationships (books and authors) are often modeled with a link item or with a copy of the few fields you need.
Denormalize on purpose
Copy fields you read often, such as an author name inside each book. Reads become cheap, and writes must update the copies. To keep copies in sync automatically, use the change feed (covered in another lesson), not manual code.
Several entity types in one container
A container is not a table. You can store customers and orders together, give both the same partition key value (for example customerId), and add a type property. One query on that partition returns both, and a transactional batch or stored procedure can change them atomically because they share a logical partition.
NoSQL query basics
The query language is SQL-like and always runs against one container. The alias refers to each item.
SELECT f.id, c.givenName
FROM Families f
JOIN c IN f.children
WHERE f.address.state = "WA"
JOINis a self-join within an item and expands arrays. It never joins containers.- Use
VALUEto return raw values, andTOP,ORDER BYandOFFSET LIMITas usual. - System functions such as
GetCurrentDateTime()return an ISO 8601 UTC string, so you do not need a UDF for that.
Time to live (TTL)
TTL deletes items automatically. A container setting of -1 means items never expire unless the item has its own ttl. A container setting of null or off disables TTL completely, even if items have ttl. An item value overrides the container default.
Exam traps
- There is no cross-container join. Choose embedding or a same-container model instead.
- Unbounded child data must be referenced, not embedded, because of the 2 MB limit.
- A point read beats a query. Query by id plus partition key when you can.
- Container TTL off (null) differs from
-1: with-1item-level ttl still works. - Normalizing like SQL Server is usually the wrong answer on this exam.
Practice questions
Choose an answer, then review the explanation to check your understanding.
1. An order always shows its line items, and line items are never read without the order. Each order has fewer than 100 lines. How should you model them?
Show answer
Answer: B.Data that is read together and is bounded in size should be embedded. One read returns everything and costs fewer RUs.
2. A blog post has thousands of comments that grow without limit. What is the best model?
Show answer
Answer: B.Unbounded arrays make items grow toward the 2 MB item limit and make every update expensive. Use references for unbounded data.
3. You need to return the given names of children from a Family item that has a children array. Which query fits?
Show answer
Answer: A.In the NoSQL language JOIN is a self-join inside one item. It expands the array so you can project its elements.
4. Related entities of different types must be read with a single query and the lowest RU cost. What should you do?
Show answer
Answer: B.Storing different entity types in one container that share a partition key value lets one in-partition query return them all.