Crash course - new database users
This crash course pathway is designed to give a concise overview of the main aspects of working with TypeDB and TypeQL for users who are new to databases. If you already have experience with databases, you might want to check out our other pathways instead.
If you would like to follow along with the examples in this crash course, make sure you have completed the environment setup. |
Introduction to TypeDB
In TypeDB, data is stored as entities in the data domain, as relations between entities, and as attributes of entities and relations. It shares many properties with relational, document, and graph DBMSs, but also has a number of distinctions and unique features.
Query language
The query language of TypeDB is TypeQL. The following is an example of a TypeQL query for a bookstore database, which retrieves the title and page count of a specific paperback book, identified by its ISBN-13.
match
$book isa paperback, has isbn-13 "9780446310789";
fetch
$book: title, page-count;
Entities, relations, and attributes
Data in TypeDB is stored as entities, relations, and attributes. This provides a high-level, conceptual data model that captures data in the same natural way we think about it in the real world. Entities, relations, and attributes are each used to model different concepts in the data domain.
- Entities
-
Used to represent independent concepts. An entity might practically require other concepts to exist, such as a car that cannot exist without its parts, but can be conceptualized without reference to them: a car can be imagined without considering its parts.
- Relations
-
Used to represent relationships between concepts. Every relation must depend on at least one other concept, and cannot be conceptualized without those dependencies: it is impossible to imagine a marriage without considering its spouses.
- Attributes
-
Used to represent properties of concepts. Every attribute has a literal value representing a property of one or more concepts, such as names of people, dates of marriages, and license plates of cars.
The type system
In order to categorize entities, relations, and attributes, the schema defines types: entity types, relation types, and attribute types. Just like classes in OOP, these acts as templates for individual data instances, declaring their structures and capabilities. The following code snippet illustrates how types are described using TypeQL’s DDL syntax.
person sub entity;
car sub entity;
marriage sub relation, relates spouse;
name sub attribute, value string;
date sub attribute, value datetime;
license-plate sub attribute, value string;
It describes six types:
-
An entity type
person
. -
An entity type
car
. -
A relation type
marriage
, which depends on at least onespouse
. -
An attribute type
name
, which has values of typestring
. -
An attribute type
date
, which has values of typedatetime
. -
An attribute type
license-plate
, which has values of typestring
.
In addition to the declared names of these types, the above DDL statements contain a number of TypeQL keywords:
-
sub
: Used to declare an entity, relation, or attribute type. Followed by the keywordsentity
,relation
, orattribute
to indicate the kind of the type. -
relates
: Used to declare a role for a relation type. Relation types must have at least one role declared. -
value
: Used to declare the value type of an attribute type. Attribute types must have a value type declared.
Describing type capabilities
TypeQL also has syntax to declare the capabilities of types. The following code snippet illustrates such DDL syntax for the types declared above.
person owns name, plays marriage:spouse;
car owns license-plate;
marriage owns date;
It uses two new TypeQL keywords:
-
owns
: Used to declare an entity or relation type to be the owner of an attribute type. -
plays
: Used to declare an entity or relation type to be a roleplayer of a relation type’s role.
These capabilities describe how data instances can depend on each other. It declares that people can have names and can be spouses in marriages, that cars can have license plates, and that marriages can have dates.
Building a database
In this crash course, we’ll be using the example of a bookstore database. The first thing to do with a new database is to define a schema. To do so, we use a Define query, which consists of the keyword define
followed by any number of DDL statements. For now, we’ll just define the types related to users and orders they place. We’ll get onto the books themselves later. Using a schema
session and write
transaction, run this query, then commit the transaction.
define
user sub entity,
owns id @key,
owns name,
owns birth-date,
plays purchase:buyer;
order sub entity,
owns id @key,
owns timestamp,
owns status,
plays purchase:order;
purchase sub relation,
relates order,
relates buyer;
id sub attribute, value string;
name sub attribute, value string;
birth-date sub attribute, value datetime;
timestamp sub attribute, value datetime;
status sub attribute,
value string,
regex "^(paid|dispatched|delivered|returned|canceled)$";
We have used two new TypeQL keywords here:
-
@key
: Used in anowns
statement to specify a key attribute of an entity or relation type.When a type owns a key attribute, the attribute acts as a mandatory unique identifier for instances of that type. It is generally advisable to ensure every entity type has a key attribute. The best choices of key attributes are those that have a real-world meaning, such as a person’s national ID number or a car’s license plate, but otherwise inventing an arbitrary ID also works.
-
regex
: Used to place a regex constraint on the value of a string attribute type.
This will serve as the starting point for our database schema. TypeDB schemas can always be extended after initial definition to add new types.
Inserting data
With a schema defined, we can begin inserting data. To do so, we use an Insert query, which consists of the insert
keyword followed by any number of DML statements. In the following query, we insert three users into the database. Using a data
session and write
transaction, run this query, then commit the transaction.
insert
$user-1 isa user,
has id "u0001",
has name "Kevin Morrison",
has birth-date 1995-10-29;
$user-2 isa user,
has id "u0002",
has name "Cameron Osborne",
has birth-date 1954-11-11;
$user-3 isa user,
has id "u0003",
has name "Keyla Pineda";
To insert entities or relations into the database, we declare variables to represent them, indicated by a $
prefix. In the above query, there are three variables representing user
entities: $user-1
, $user-2
, and $user-3
. Variable names are arbitrary and exist only within the scope of the query.
With variables declared, we declare their properties using the following TypeQL keywords:
-
isa
: Used to declare the type of an entity or relation. -
has
: Used to declare an attribute of an entity or relation, comprising a type and value.
The data we can insert is constrained by our schema. We can only declare variables to be of the entity and relation types we defined, and we can only declare their attribues corresponding to the attribute types their types own. We could not, for instance, declare $user-1
to have an attribute of type status
, as user
does not own this attribute type.
Attributes are normally optional, and an entity or relation does not have to have an attribute just because its type is declared to own the attribute type. This is the case above, where $user-3
does not have a birth date specified. This is not the case for id
, because it has been declared to be a key attribute of user
, making it mandatory.
In the next Insert query, we insert three orders. Using a data
session and write
transaction, run this query, then commit the transaction.
insert
$order-1 isa order,
has id "o0001",
has timestamp 2022-08-03T19:51:24.324,
has status "canceled";
$order-2 isa order,
has id "o0002",
has timestamp 2021-04-27T05:02:39.672,
has status "dispatched";
$order-6 isa order,
has id "o0006",
has timestamp 2020-08-19T20:21:54.194,
has status "paid";
Reading data
With data inserted, we can retrieve it using a Fetch query. The previous Define and Insert queries have each had a single clause, indicated by the define
and insert
keywords respectively. Fetch queries have two clauses: a match
clause followed by a fetch
clause. The match
clause is used to find any data in the database that matches a given pattern, described using the same DML statements we used to insert data. If the statements in the match
clause have the same structure as those originally used to insert the data, then that data will be matched. The fetch
clause then describes what should be returned for each match found.
The following is an example of a Fetch query. It matches any orders in the database that have the status "paid" and then returns their IDs and timestamps. Using a data
session and read
transaction, run this query.
match
$order isa order, has status "paid";
fetch
$order: id, timestamp;
All Fetch queries return results in JSON format. You should see the following result.
{
"order": {
"id": [ { "value": "o0006", "type": { "label": "id", "root": "attribute", "value_type": "string" } } ],
"timestamp": [ { "value": "2020-08-19T20:21:54.194", "type": { "label": "timestamp", "root": "attribute", "value_type": "datetime" } } ],
"type": { "label": "order", "root": "entity" }
}
}
As expected, we retrieve a single result in the form of a JSON object, as only one of the orders has the status "paid". The object includes three fields: one for each of the two attribute types of $order
that we specified to return, id
and timestamp
, in addition to one for metadata describing the type of $order
. The attribute fields include both the attribute value and similar typing metadata. In the next query, we retrieve the name and birth date of every user. Using a data
session and read
transaction, run this query.
match
$user isa user;
fetch
$user: name, birth-date;
Results
{
"user": {
"birth-date": [ { "value": "1995-10-29T00:00:00.000", "type": { "label": "birth-date", "root": "attribute", "value_type": "datetime" } } ],
"name": [ { "value": "Kevin Morrison", "type": { "label": "name", "root": "attribute", "value_type": "string" } } ],
"type": { "label": "user", "root": "entity" }
}
}
{
"user": {
"birth-date": [ { "value": "1954-11-11T00:00:00.000", "type": { "label": "birth-date", "root": "attribute", "value_type": "datetime" } } ],
"name": [ { "value": "Cameron Osborne", "type": { "label": "name", "root": "attribute", "value_type": "string" } } ],
"type": { "label": "user", "root": "entity" }
}
}
{
"user": {
"birth-date": [ ],
"name": [ { "value": "Keyla Pineda", "type": { "label": "name", "root": "attribute", "value_type": "string" } } ],
"type": { "label": "user", "root": "entity" }
}
}
As we have not specified any constraints for $user
other than specifying its type, the variable will match every user, so we expect three JSON objects in the results. As before, we see fields for the requested attribute types. The field corresponding to Keyla’s birth date is empty, as there is no matching data of this type.
Using relations
Now that we have inserted users and orders into the database, we will insert some purchase
relations to indicate which users made which orders. Relations are described using special relation tuple syntax that lists their roleplayers. The following code snippet shows such a tuple as part of an isa
statement.
$purchase (order: $order, buyer: $user) isa purchase;
Each element of the tuple consists of the role that the roleplayer plays, followed by the variable representing that roleplayer. As the purchase
relation type references two roles (order
and buyer
), the tuple above has two elements, but the syntax can represent relations with any number of roleplayers as needed.
$relation (role-1: $a, role-2: $b, role-3: $c, ...) isa relation-type;
It is also possible for a role to occur more than once in a relation tuple where necessary, as long as it references different roleplayers. For instance, a |
The following Insert query creates three new purchase
relations between users and orders we previously inserted. Using a data
session and write
transaction, run this query, then commit the transaction.
match
$user-1 isa user, has id "u0001";
$user-2 isa user, has id "u0002";
$order-1 isa order, has id "o0001";
$order-2 isa order, has id "o0002";
$order-6 isa order, has id "o0006";
insert
$purchase-1 (order: $order-1, buyer: $user-1) isa purchase;
$purchase-2 (order: $order-2, buyer: $user-1) isa purchase;
$purchase-6 (order: $order-6, buyer: $user-2) isa purchase;
Unlike the previous Insert queries, this one has two clauses: a match
clause followed by an insert
clause. The match
clause is used to match the existing entities as with the previous Fetch query, but then instead of returning attributes from them, we instead reference them in newly inserted relations. When inserting relations, it is best to match the roleplayers by a key attribute, as we have done here, to ensure that each roleplayer variable matches exactly one data instance.
Reading relations
To retrieve data from relations, we use the same tuple syntax as used to insert them. The following Fetch query matches any purchase
relations in the database, and then retrieves attributes from their roleplayers. Using a data
session and read
transaction, run this query.
match
$purchase (order: $order, buyer: $user) isa purchase;
fetch
$order: timestamp, status;
$user: name;
Results
{
"order": {
"status": [ { "value": "canceled", "type": { "label": "status", "root": "attribute", "value_type": "string" } } ],
"timestamp": [ { "value": "2022-08-03T19:51:24.324", "type": { "label": "timestamp", "root": "attribute", "value_type": "datetime" } } ],
"type": { "label": "order", "root": "entity" }
},
"user": {
"name": [ { "value": "Kevin Morrison", "type": { "label": "name", "root": "attribute", "value_type": "string" } } ],
"type": { "label": "user", "root": "entity" }
}
}
{
"order": {
"status": [ { "value": "paid", "type": { "label": "status", "root": "attribute", "value_type": "string" } } ],
"timestamp": [ { "value": "2020-08-19T20:21:54.194", "type": { "label": "timestamp", "root": "attribute", "value_type": "datetime" } } ],
"type": { "label": "order", "root": "entity" }
},
"user": {
"name": [ { "value": "Cameron Osborne", "type": { "label": "name", "root": "attribute", "value_type": "string" } } ],
"type": { "label": "user", "root": "entity" }
}
}
{
"order": {
"status": [ { "value": "dispatched", "type": { "label": "status", "root": "attribute", "value_type": "string" } } ],
"timestamp": [ { "value": "2021-04-27T05:02:39.672", "type": { "label": "timestamp", "root": "attribute", "value_type": "datetime" } } ],
"type": { "label": "order", "root": "entity" }
},
"user": {
"name": [ { "value": "Kevin Morrison", "type": { "label": "name", "root": "attribute", "value_type": "string" } } ],
"type": { "label": "user", "root": "entity" }
}
}
The patterns used in the match
clauses of TypeQL queries are fully composable, so we can introduce additional constraints simply by adding further statements. In the next query, we modify the previous query to restrict the results to show only orders with the status "paid". Using a data
session and read
transaction, run this query.
match
$purchase (order: $order, buyer: $user) isa purchase;
$order has status "paid";
fetch
$order: timestamp, status;
$user: name;
Results
{
"order": {
"status": [ { "value": "paid", "type": { "label": "status", "root": "attribute", "value_type": "string" } } ],
"timestamp": [ { "value": "2020-08-19T20:21:54.194", "type": { "label": "timestamp", "root": "attribute", "value_type": "datetime" } } ],
"type": { "label": "order", "root": "entity" }
},
"user": {
"name": [ { "value": "Cameron Osborne", "type": { "label": "name", "root": "attribute", "value_type": "string" } } ],
"type": { "label": "user", "root": "entity" }
}
}
Working with type hierarchies
The data we have worked with so far is typical for databases, and would be simple to model in virtually any other DBMS, such as a relational, document, or graph system. Now we will introduce one of TypeDB’s most powerful and unique data structures, that would not be easy to model using another DBMS: type hierarchies.
We previously saw how sub
statements in Define queries are used to declare new types. In those statements, the sub
keyword was followed by the keywords entity
, relation
, or attribute
to specify the kind of the new type. However, we can replace these keywords with an existing type instead, in which case the new type will be a subtype of that existing type.
We do this in the following query, where we define a new type hierarchy of book types, described by a supertype book
with three subtypes: paperback
, hardback
, and ebook
. Using a schema
session and write
transaction, run this query, then commit the transaction.
define
book sub entity, abstract,
owns isbn-13 @key,
owns isbn-10 @unique,
owns title,
owns genre,
owns page-count,
owns price;
paperback sub book, owns stock;
hardback sub book, owns stock;
ebook sub book;
isbn sub attribute, abstract, value string;
isbn-13 sub isbn;
isbn-10 sub isbn;
title sub attribute, value string;
genre sub attribute, value string;
page-count sub attribute, value long;
price sub attribute, value double;
stock sub attribute, value long;
Here we have introduced two new TypeQL keywords:
-
abstract
: Used to define an entity, relation, or attribute type to be abstract.A type defined to be abstract cannot be directly inserted into the database, much like an abstract class in OOP. It can only be inserted indirectly by inserting one of its subtypes.
-
@unique
: Used in anowns
statement to specify a unique attribute of an entity or relation type.When a type owns a unique attribute, the attribute acts as a unique but non-mandatory identifier for instances of that type.
In a type hierarchy, the capabilities of supertypes are automatically inherited by their subtypes. This means that the attribute type ownerships of book
defined via owns
statements also apply to paperaback
, hardback
, and ebook
. Meanwhile, ownership of stock
has been defined individually at the subtype level, so only paperback
and hardback
have stock levels. This gives complete control over which data instances are permitted to own which attributes.
This also applies to which data instances are permitted to play which roles via plays
statements, which can likewise be defined at the supertype or subtypes levels. In the next Define query, we define a new order-line
relation for recording which books have been added to which orders. Using a schema
session and write
transaction, run this query, then commit the transaction.
define
order-line sub relation,
relates order,
relates item,
owns quantity;
order plays order-line:order;
book plays order-line:item;
quantity sub attribute, value long;
Inserting data into type hierarchies
Inserting data into a type hierarchy is identical to inserting data into non-hierarchical types. We do so by declaring the exact (most specific) type of each data instance in an isa
statement. In the following Insert query, we create a number of book
entities of various types. Using a data
session and write
transaction, run this query, then commit the transaction.
insert
$book-1 isa ebook,
has isbn-13 "9780393634563",
has isbn-10 "0393634566",
has title "The Odyssey",
has genre "fiction",
has genre "classics",
has page-count 656,
has price 13.99;
$book-2 isa paperback,
has isbn-13 "9780500291221",
has isbn-10 "0500291225",
has title "Great Discoveries in Medicine",
has genre "nonfiction",
has genre "history",
has page-count 352,
has price 12.05,
has stock 18;
$book-3 isa ebook,
has isbn-13 "9780575104419",
has isbn-10 "0575104414",
has title "Dune",
has genre "fiction",
has genre "science fiction",
has page-count 624,
has price 5.49;
$book-4 isa hardback,
has isbn-13 "9780740748479",
has isbn-10 "0740748475",
has title "The Complete Calvin and Hobbes",
has genre "fiction",
has genre "comics",
has page-count 1451,
has price 128.71,
has stock 6;
$book-5 isa paperback,
has isbn-13 "9798691153570",
has title "Business Secrets of The Pharoahs",
has genre "nonfiction",
has genre "business",
has page-count 260,
has price 11.99,
has stock 8;
In addition to seeing how we insert data into type hierarchies, this query shows off an important property of attribute types. In addition to being optional by default, attribute types in TypeDB are also multivalued by default. This means that we can insert entities or relations that have multiple attributes of the same type, as with genre
above.
Reading data from type hierarchies
Once we have inserted data instances of a given type, we can query it via either its exact type or any of its supertypes. The following Fetch query retrieves the ISBN-13, title, and genres of all books. Meanwhile, the query below retrieves these details only for paperbacks. Using a data
session and read
transaction, run these queries.
match
$book isa book;
fetch
$book: isbn-13, title, genre;
Results
{
"book": {
"genre": [
{ "value": "fiction", "type": { "label": "genre", "root": "attribute", "value_type": "string" } },
{ "value": "classics", "type": { "label": "genre", "root": "attribute", "value_type": "string" } }
],
"isbn-13": [ { "value": "9780393634563", "type": { "label": "isbn-13", "root": "attribute", "value_type": "string" } } ],
"title": [ { "value": "The Odyssey", "type": { "label": "title", "root": "attribute", "value_type": "string" } } ],
"type": { "label": "ebook", "root": "entity" }
}
}
{
"book": {
"genre": [
{ "value": "fiction", "type": { "label": "genre", "root": "attribute", "value_type": "string" } },
{ "value": "science fiction", "type": { "label": "genre", "root": "attribute", "value_type": "string" } }
],
"isbn-13": [ { "value": "9780575104419", "type": { "label": "isbn-13", "root": "attribute", "value_type": "string" } } ],
"title": [ { "value": "Dune", "type": { "label": "title", "root": "attribute", "value_type": "string" } } ],
"type": { "label": "ebook", "root": "entity" }
}
}
{
"book": {
"genre": [
{ "value": "history", "type": { "label": "genre", "root": "attribute", "value_type": "string" } },
{ "value": "nonfiction", "type": { "label": "genre", "root": "attribute", "value_type": "string" } }
],
"isbn-13": [ { "value": "9780500291221", "type": { "label": "isbn-13", "root": "attribute", "value_type": "string" } } ],
"title": [ { "value": "Great Discoveries in Medicine", "type": { "label": "title", "root": "attribute", "value_type": "string" } } ],
"type": { "label": "paperback", "root": "entity" }
}
}
{
"book": {
"genre": [
{ "value": "business", "type": { "label": "genre", "root": "attribute", "value_type": "string" } },
{ "value": "nonfiction", "type": { "label": "genre", "root": "attribute", "value_type": "string" } }
],
"isbn-13": [ { "value": "9798691153570", "type": { "label": "isbn-13", "root": "attribute", "value_type": "string" } } ],
"title": [ { "value": "Business Secrets of The Pharoahs", "type": { "label": "title", "root": "attribute", "value_type": "string" } } ],
"type": { "label": "paperback", "root": "entity" }
}
}
{
"book": {
"genre": [
{ "value": "comics", "type": { "label": "genre", "root": "attribute", "value_type": "string" } },
{ "value": "fiction", "type": { "label": "genre", "root": "attribute", "value_type": "string" } }
],
"isbn-13": [ { "value": "9780740748479", "type": { "label": "isbn-13", "root": "attribute", "value_type": "string" } } ],
"title": [ { "value": "The Complete Calvin and Hobbes", "type": { "label": "title", "root": "attribute", "value_type": "string" } } ],
"type": { "label": "hardback", "root": "entity" }
}
}
match
$book isa paperback;
fetch
$book: isbn-13, title, genre;
Results
{
"book": {
"genre": [
{ "value": "history", "type": { "label": "genre", "root": "attribute", "value_type": "string" } },
{ "value": "nonfiction", "type": { "label": "genre", "root": "attribute", "value_type": "string" } }
],
"isbn-13": [ { "value": "9780500291221", "type": { "label": "isbn-13", "root": "attribute", "value_type": "string" } } ],
"title": [ { "value": "Great Discoveries in Medicine", "type": { "label": "title", "root": "attribute", "value_type": "string" } } ],
"type": { "label": "paperback", "root": "entity" }
}
}
{
"book": {
"genre": [
{ "value": "business", "type": { "label": "genre", "root": "attribute", "value_type": "string" } },
{ "value": "nonfiction", "type": { "label": "genre", "root": "attribute", "value_type": "string" } }
],
"isbn-13": [ { "value": "9798691153570", "type": { "label": "isbn-13", "root": "attribute", "value_type": "string" } } ],
"title": [ { "value": "Business Secrets of The Pharoahs", "type": { "label": "title", "root": "attribute", "value_type": "string" } } ],
"type": { "label": "paperback", "root": "entity" }
}
}
Because we made the attribute types isbn-13
and isbn-10
subtypes of a common isbn
supertype, we can also retrieve these attribute types together, as we do in the following query. Using a data
session and read
transaction, run this query.
match
$book isa paperback;
fetch
$book: isbn, title, genre;
Results
{
"book": {
"genre": [
{ "value": "history", "type": { "label": "genre", "root": "attribute", "value_type": "string" } },
{ "value": "nonfiction", "type": { "label": "genre", "root": "attribute", "value_type": "string" } }
],
"isbn": [
{ "value": "9780500291221", "type": { "label": "isbn-13", "root": "attribute", "value_type": "string" } },
{ "value": "0500291225", "type": { "label": "isbn-10", "root": "attribute", "value_type": "string" } }
],
"title": [ { "value": "Great Discoveries in Medicine", "type": { "label": "title", "root": "attribute", "value_type": "string" } } ],
"type": { "label": "paperback", "root": "entity" }
}
}
{
"book": {
"genre": [
{ "value": "business", "type": { "label": "genre", "root": "attribute", "value_type": "string" } },
{ "value": "nonfiction", "type": { "label": "genre", "root": "attribute", "value_type": "string" } }
],
"isbn": [ { "value": "9798691153570", "type": { "label": "isbn-13", "root": "attribute", "value_type": "string" } } ],
"title": [ { "value": "Business Secrets of The Pharoahs", "type": { "label": "title", "root": "attribute", "value_type": "string" } } ],
"type": { "label": "paperback", "root": "entity" }
}
}
We can see the exact type of each attribute returned in the accompanying metadata.
Referencing data in type hierarchies
Just like when reading data from hierarchies in Fetch queries, we can also match data instances by any of their types when referencing data in Insert queries. In the following query, we insert order-line
relations between existing books and orders. When we match the books, we do not specify their exact types, or even the exact types of the ISBNs we are providing. Using a data
session and write
transaction, run this query, then commit the transaction.
match
$order-1 isa order, has id "o0001";
$order-2 isa order, has id "o0002";
$order-6 isa order, has id "o0006";
$book-1 isa book, has isbn "9780393634563";
$book-2 isa book, has isbn "9780500291221";
$book-3 isa book, has isbn "9780575104419";
$book-4 isa book, has isbn "9780740748479";
insert
(order: $order-1, item: $book-1) isa order-line, has quantity 2;
(order: $order-1, item: $book-2) isa order-line, has quantity 1;
(order: $order-2, item: $book-3) isa order-line, has quantity 1;
(order: $order-6, item: $book-4) isa order-line, has quantity 2;
Grouping results
Finally, we will retrieve the list of books in each order made by the user with ID "u0001" using the following Fetch query. Using a data
session and read
transaction, run this query.
match
$user isa user, has id "u0001";
$purchase (order: $order, buyer: $user) isa purchase;
$order-line (order: $order, item: $book) isa order-line;
fetch
$order: id;
$book: title, price;
$order-line: quantity;
Results
{
"book": {
"price": [ { "value": 13.99, "type": { "label": "price", "root": "attribute", "value_type": "double" } } ],
"title": [ { "value": "The Odyssey", "type": { "label": "title", "root": "attribute", "value_type": "string" } } ],
"type": { "label": "ebook", "root": "entity" }
},
"order": {
"id": [ { "value": "o0001", "type": { "label": "id", "root": "attribute", "value_type": "string" } } ],
"type": { "label": "order", "root": "entity" }
},
"order-line": {
"quantity": [ { "value": 2, "type": { "label": "quantity", "root": "attribute", "value_type": "long" } } ],
"type": { "label": "order-line", "root": "relation" }
}
}
{
"book": {
"price": [ { "value": 12.05, "type": { "label": "price", "root": "attribute", "value_type": "double" } } ],
"title": [ { "value": "Great Discoveries in Medicine", "type": { "label": "title", "root": "attribute", "value_type": "string" } } ],
"type": { "label": "paperback", "root": "entity" }
},
"order": {
"id": [ { "value": "o0001", "type": { "label": "id", "root": "attribute", "value_type": "string" } } ],
"type": { "label": "order", "root": "entity" }
},
"order-line": {
"quantity": [ { "value": 1, "type": { "label": "quantity", "root": "attribute", "value_type": "long" } } ],
"type": { "label": "order-line", "root": "relation" }
}
}
{
"book": {
"price": [ { "value": 5.49, "type": { "label": "price", "root": "attribute", "value_type": "double" } } ],
"title": [ { "value": "Dune", "type": { "label": "title", "root": "attribute", "value_type": "string" } } ],
"type": { "label": "ebook", "root": "entity" }
},
"order": {
"id": [ { "value": "o0002", "type": { "label": "id", "root": "attribute", "value_type": "string" } } ],
"type": { "label": "order", "root": "entity" }
},
"order-line": {
"quantity": [ { "value": 1, "type": { "label": "quantity", "root": "attribute", "value_type": "long" } } ],
"type": { "label": "order-line", "root": "relation" }
}
}
However, the results are not in a particularly useful format. We have retrieved the information we wanted for each order line: the order ID, the book title, the book price, and the quantity ordered, but these details are not grouped by order ID. To do so, we can use a sub-query. In the parent query, we match each order and return the ID. Then, for each of those orders, we match the associated order lines in a sub query, labeled with the identifier "order-lines"
. Finally, we return the book title, book price, and quantity ordered in that sub-query. Using a data
session and read
transaction, run this query.
match
$user isa user, has id "u0001";
$purchase (order: $order, buyer: $user) isa purchase;
fetch
$order: id;
"order-lines": {
match
$order-line (order: $order, item: $book) isa order-line;
fetch
$book: title, price;
$order-line: quantity;
};
Results
{
"order": {
"id": [ { "value": "o0001", "type": { "label": "id", "root": "attribute", "value_type": "string" } } ],
"type": { "label": "order", "root": "entity" }
},
"order-lines": [
{
"book": {
"price": [ { "value": 13.99, "type": { "label": "price", "root": "attribute", "value_type": "double" } } ],
"title": [ { "value": "The Odyssey", "type": { "label": "title", "root": "attribute", "value_type": "string" } } ],
"type": { "label": "ebook", "root": "entity" }
},
"order-line": {
"quantity": [ { "value": 2, "type": { "label": "quantity", "root": "attribute", "value_type": "long" } } ],
"type": { "label": "order-line", "root": "relation" }
}
},
{
"book": {
"price": [ { "value": 12.05, "type": { "label": "price", "root": "attribute", "value_type": "double" } } ],
"title": [ { "value": "Great Discoveries in Medicine", "type": { "label": "title", "root": "attribute", "value_type": "string" } } ],
"type": { "label": "paperback", "root": "entity" }
},
"order-line": {
"quantity": [ { "value": 1, "type": { "label": "quantity", "root": "attribute", "value_type": "long" } } ],
"type": { "label": "order-line", "root": "relation" }
}
}
]
}
{
"order": {
"id": [ { "value": "o0002", "type": { "label": "id", "root": "attribute", "value_type": "string" } } ],
"type": { "label": "order", "root": "entity" }
},
"order-lines": [
{
"book": {
"price": [ { "value": 5.49, "type": { "label": "price", "root": "attribute", "value_type": "double" } } ],
"title": [ { "value": "Dune", "type": { "label": "title", "root": "attribute", "value_type": "string" } } ],
"type": { "label": "ebook", "root": "entity" }
},
"order-line": {
"quantity": [ { "value": 1, "type": { "label": "quantity", "root": "attribute", "value_type": "long" } } ],
"type": { "label": "order-line", "root": "relation" }
}
}
]
}
In the query results, the results of each sub-query are contained in a nested field, whose key is the "order-lines"
label we provided. This makes the query results much easier to interpret, as we can clearly see which lines belong to which order.