Advent of TypeQL: Day 14

Happy Advent!
Welcome to our Advent of TypeQL. Each day, we’ll post a small series of TypeQL challenges. There will be 14 days from December 11 to December 24, inclusive, so stay tuned!
It’ll be easiest to work through the days consecutively, since they are designed to incrementally build up an intuition of TypeQL. However, each day will also have a shortcut setup to just get a database for that day’s activity, allowing you to hop in without going through all the previous days challenges if you want.
You’ll want to use TypeDB Studio or TypeDB Console running against a TypeDB Cloud or local instance of TypeDB CE.
Background
Santa was preparing to release his Christmas plans. Finally done, he had just torn down his development and staging environments in preparation for a production launch of his grand Christmas plans…
Unfortunately at that precise moment, all of his Christmas plans, supposedly safely stashed in his git repository, got deleted by a pesky engineer with an overly permissive Github token and a mistyped command. Oh dear!

Santa contacted support and was able to recover some of his plans. However, they are incomplete. We’re going to help Santa get his plans back on track for Christmas, one day at a time!
Setup
If you’ve done Day 13, you can continue without setting anything up – just open up Studio or Console and get going!
If you’re new here, you’ll first want to spin up a TypeDB instance and connect with Studio or Console, and create a new database.
Then, you can get Santa’s recovered database schema including our subsequent changes here: schema, and load it by copying the linked text into a schema transaction’s query interface in Studio or Console, and then commit (note: by default, Studio auto-commits each query when set to “auto” mode).
Get the initial dataset plus subsequent days’ changes as a data file. Then load it by doing the same (follow link, copy text, paste into Studio or Console), but this time use a write transaction – and make sure you have committed.
At this point, you should have a database ready to go!
Day 14
We’re done fixing data and schema! Today we’ll just try to read the data out, in particular using fetch. Fetch clauses come at the end of queries and format attributes or values into documents (JSON) – but also have some special capabilities like executing subqueries.
Santa’s data
Let’s start simple: let’s read Santa with all his attributes in JSON format, along with those of the santa-distribution-route that is connected via a santa-journey relation. Tip: Remember you can use { $var.* } to retrieve all attributes of an owner easily.
Answer
match
$santa isa santa;
$route isa santa-distribution-route;
santa-journey ($santa, $route);
fetch {
"santa": { $santa.* },
"route": { $route.* },
};
Alright, now let’s extend this to retrieve every stop on the journey (distribution-stop relates route, which santa-distribution-route plays), and the country that the stop is in.
Answer
match
$santa isa santa;
$route isa santa-distribution-route;
santa-journey ($santa, $route);
$stop isa distribution-stop (route: $route, country: $country);
fetch {
"santa": { $santa.* },
"route": { $route.* },
"stop": { $stop.* },
"country": { $country.* },
};
TypeDB’s fetch clause is highly flexible. You can nest objects together arbitrarily – try to organize the output such that the stop number and the country name are part of one sub-object.
Answer
match
$santa isa santa;
$route isa santa-distribution-route;
santa-journey ($santa, $route);
$stop isa distribution-stop (route: $route, country: $country);
fetch {
"santa": { $santa.* },
"route": { $route.* },
"stop": {
"number": $stop.stop-number,
"country": $country.name,
},
};
If you notice, this doesn’t produce the stops in order – let’s add a sorting to ensure that the outputs are in order of stop number.
Answer
match
$santa isa santa;
$route isa santa-distribution-route;
santa-journey ($santa, $route);
$stop isa distribution-stop (route: $route, country: $country),
has stop-number $number;
sort $number;
fetch {
"santa": { $santa.* },
"route": { $route.* },
"stop": {
"number": $stop.stop-number,
"country": $country.name,
},
};
This effectively gets the data out, split across many answers – but each answer contains duplicate Santa and Route descriptions. What if we wanted one answer with a list of stops nested inside? We can use fetch subqueries to do this, roughly following the syntax:
fetch {
"key": [
match <statements using variables from query body>
fetch { ... };
]
};
The inner list syntax indicates that we’ll be gathering answers to the subquery into a list. As you will see – the syntax of the fetch clause matches the output structure you’ll receive.
Answer
match
$santa isa santa;
$route isa santa-distribution-route;
santa-journey ($santa, $route);
fetch {
"santa": { $santa.* },
"route": { $route.* },
"stops": [
match
$stop isa distribution-stop (route: $route, country: $country),
has stop-number $number;
sort $number;
fetch {
"number": $number,
"country": $country.name,
};
]
};
So, depending on your application’s output requirements, you can reshape your JSON response format.
Location data
We’ve now got a query to get all of Santa’s stops out. It would also be nice to be able to export all the data to do with locations.
Let’s write a query that fetches any region that does not have any further child regions, and retrieves the hierarchy of parent regions that contain it. To do this, we’ll need a transitive function that returns all the parent regions of a region.
Its signature will look something like:
with fun parent_regions($region: region) -> { region }
Answer
with fun parent_regions($region: region) -> { region }:
match
{
let $middle in parent_regions($region);
location-contains (parent: $parent, child: $middle);
} or {
location-contains (parent: $parent, child: $region);
};
return { $parent };
match
$region isa region;
not { location-contains (parent: $region); };
fetch {
"name": $region.name,
"parents": [
match let $parent in parent_regions($region);
fetch {
"name": $parent.name
};
]
};
If you inspect this data and locate Antarctica, and compare it to for example New Delhi, you’ll see polymorphism in action. The query sometimes finds continent regions that don’t have any child regions, and other times it finds cities (since they can’t have any subregions at all in the schema). The function to retrieve parent regions is also polymorphic, accepting any subtype of region agnostically.
Elves and Productions
Lastly, let’s read out each elf and for each elf:
- where they live
- what productions they are involved with
- what presents and quantities are being produced for each production
Answer
match
$elf isa elf;
lives-in ($elf, $region);
$region isa region;
fetch {
"elf": { $elf.* },
"home": { $region.* },
"productions": [
match
$production isa production ($elf, $blueprint);
$blueprint isa present-blueprint;
fetch {
"present": { $blueprint.* },
"quantity": $production.quantity-required,
};
]
};
Fantastic! We’ve essentially just written a collection of small queries to read out the entire database in a custom format. fetch is a powerful mechanism to read data out in a structured, predictable way.
The end!
Santa is so happy to have had such a willing helper for his 2025 Christmas data recovery. He’s seriously considering modeling the entire recovery process in his database. You would be a christmas-helper too!

Thank you for joining us. Wishing you a happy holiday period from the entire TypeDB team!

If you encounter any issues, want to chat, or anything else – feel free to post in our Discord or feel free to email me directly.
