Officially out now: The TypeDB 3.0 Roadmap

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 databases

A database is a system for storing data in an organised format, and making that data available for retrieval. A database is used to store data for a specific purpose, known as a data domain. Some examples are:

  • A bookstore database, containing data on books, customers, and orders.

  • A university database, containing data on students, classes, and grades.

  • An airline database, containing data on aircraft, flights, and cargo.

Databases are designed to meet a number of organisational goals:

  • To provide a centralised store that acts as a source-of-truth for the data.

  • To enable concurrent access to data by multiple parties across the organisation.

  • To enforce that the data is not corrupted and conforms to the correct business logic.

  • To securely regulate access to different categories of data to different users.

Database management systems

The software a database runs on is called a database management system (DBMS). Sometimes, the term "database" is used to refer to the DBMS rather than an individual database. A single DBMS is capable of hosting multiple individual databases. DBMSs are typically categorised by the data storage format. Some common categories include:

TypeDB is a DBMS, but it does not fall into any of these categories. 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.

Database operations

There are four kinds of data operations that can be used to manipulate data in a database:

  • Create operation — the insertion of new data into the database.

  • Read operation — the retrieval of data from the database.

  • Update operation — the modification of existing data in the database.

  • Delete operation — the removal of data from the database.

Together, these are known as the CRUD operations, by their initials.

Query languages

An instruction sent to a database to perform a CRUD operation is known as a query. In the same way that programs are written using a programming language, queries are written in a query language. Each database uses a specific query language, the most common of which is SQL. 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;

Queries are categorised by the type of CRUD operation they perform. The above is an example of a TypeQL Fetch query, which is used to perform a read operation in TypeDB.

Database schemas

Many databases, including TypeDB, require users to specify permitted data structures before data can be inserted into them, similar to defining classes before instantiating them in object-oriented programming (OOP). This comes with a number of benefits, including data integrity guarantees and more powerful querying capabilities. The data structure specifications are known collectively as a schema.

In databases with a schema, the query language comprises two parts: the data definition language (DDL), which is used to define a schema, and the data manipulation language (DML), which is used to perform CRUD operations on data.

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 one spouse.

  • An attribute type name, which has values of type string.

  • An attribute type date, which has values of type datetime.

  • An attribute type license-plate, which has values of type string.

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 keywords entity, relation, or attribute 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, studio run run this query, then studio check 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 an owns 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, studio run run this query, then studio check 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, studio run run this query, then studio check 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, studio run 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, studio run 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 marriage relation type would only need a single role spouse, but any instance of marriage would have two person instances playing this role.

The following Insert query creates three new purchase relations between users and orders we previously inserted. Using a data session and write transaction, studio run run this query, then studio check 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, studio run 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, studio run 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, studio run run this query, then studio check 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 an owns 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, studio run run this query, then studio check 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, studio run run this query, then studio check 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, studio run 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, studio run 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, studio run run this query, then studio check 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, studio run 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, studio run 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.

What’s next?

Continue learning how to use TypeDB with TypeDB Academy, or explore other sections of the documentation.

An end-to-end learning experience for TypeDB and TypeQL, showing how to take advantage of TypeDB’s unique features.

Practice-oriented guides on using TypeDB, including the TypeDB Studio and TypeDB Console manuals.

Installation guides, tutorials, and API references for the official TypeDB drivers in all supported languages.

Complete language reference for TypeQL, covering all query types, pattern elements, and keywords.