TypeDB 3.0 is live! Get started for free.

Reading data

This page explains how to read data from a TypeDB database.

Overview

A read query in TypeDB is also called a read pipeline. A read pipeline may comprise multiple stages, that retrieve and operate on data.

  • A match stage retrieves data based on a supplied pattern.

  • Stages like sort, reduce, limit, …​ operate on data.

A read pipeline may comprise different combinations of stages (refer to the Data pipelines for a full list). It should always start with a match stage (not counting any preamble with stages used to make function definitions).

Most of the read stages produce concept rows and also take concept rows as inputs: this allows stages to be chained together, with the output of one stage being fed as input to the next stage. The only exception is fetch, which produces JSON documents, and can therefore only appear at the end of a pipeline.

Read pipelines can be executed in any of the schema, write, or read transaction kinds.

Using match: the basics

TypeQL queries retrieve data by matching patterns. Patterns comprise one or more statements containing variables. Match stages return all combinations of data for their variables that satisfy the pattern (note that this only applies to “free” variables, i.e. those which are not bound by previous stages).

Patterns can also be combined into larger patterns by conjunction with ;, disjuction with or, and negation with not. See the pattern reference for more.

When retrieving data from the database there is a basic distinction to keep in mind:

  • objects, i.e. entities and relations, do not have a value that can be returned from the database; instead only their database-intrinstic representation, IID, is returned to the client.

  • attributes have both an IID and a (human readable) value that can be returned to the client.

In the examples below, both cases will be illustrated.

Reading objects

To read type instances (an entity, a relation, or an attribute), use the isa keyword:

Users reading example
match
  $u isa user;

This simple query will return all the user s in the database.

Example TypeDB Console output
   --------
    $u | iid 0x1e00030000000000000002 isa user
   --------
    $u | iid 0x1e00030000000000000003 isa user
   --------
    $u | iid 0x1e00030000000000000004 isa user
   --------
    $u | iid 0x1e00030000000000000005 isa user
   --------

Combining statements, it is possible to add more constraints to the match. For example, the following query searches for all user s with a specific username using the has statement (which can be interpreted as "the user with a specific username " as this attribute is a key):

Specific users reading example
match
  $u isa user, has username "User";
Example TypeDB Console output
   --------
    $u | iid 0x1e00030000000000000005 isa user
   --------

Similar to entity objects, we may also query for relation

Matching relation linking two instances with specific roles example
match
  $a isa user, has username "Alice", has email "alice@typedb.com";
  $b isa user, has username "Bob", has email "bob@typedb.com";
  $f isa friendship, links (friend: $a, friend: $b);
Example TypeDB Console output
   --------
    $a | iid 0x1e00030000000000000002 isa user
    $b | iid 0x1e00030000000000000003 isa user
    $f | iid 0x1f00020000000000000000 isa friendship
   --------

If you are sure that these two user s can only play specific roles in a friendship or your intent is to query for any roles these `user`s play in a relation, the query can be simplified:

Matching relation linking two instances of any roles example
match
  $a isa user, has username "Alice", has email "alice@typedb.com";
  $b isa user, has username "Bob", has email "bob@typedb.com";
  $f isa friendship, links (friend: $a, friend: $b);
Example TypeDB Console output
   --------
    $a | iid 0x1e00030000000000000002 isa user
    $b | iid 0x1e00030000000000000003 isa user
    $f | iid 0x1f00020000000000000000 isa friendship
   --------

TypeDB and TypeQL also support further simplification in case you are interested in only the user s, so the friendship between them is not needed in the output:

Matching relation linking two instances without a variable example
match
  $a isa user, has username "Alice", has email "alice@typedb.com";
  $b isa user, has username "Bob", has email "bob@typedb.com";
  friendship ($a, $b);

It is an equivalent of the anonymous variable usage:

Matching relation linking two instances with an anonymous variable example
match
  $a isa user, has username "Alice", has email "alice@typedb.com";
  $b isa user, has username "Bob", has email "bob@typedb.com";
  $_ isa friendship, links ($a, $b);
Example TypeDB Console output
   --------
    $a | iid 0x1e00030000000000000002 isa user
    $b | iid 0x1e00030000000000000003 isa user
   --------

Reading attributes and values

Attributes and their values can be read in a similar way. By variablizing the attribute part of the previous example, it’s easy to get all user s and their username s. You can also use anonymous variables ($_) if they are not needed in the output:

Matching all users' usernames example
match
  $_ isa user, has username $n;

Equivalently, we could query

Matching all usernames example
match
  $n isa username;

(The queries may only differ in the case of independent attributes, as these may not be owned, and ownership is required by the first version of the query.)

Example TypeDB Console output
   --------
    $n | Alice isa username
   --------
    $n | Bob isa username
   --------
    $n | Charlie isa username
   --------
    $n | User isa username
   --------

While the attribute is variablized, it is also possible to put the same constraints from the specific user search example on it. For instance, we can use the same attribute variable twice in two compatible statements:

Specific users reading with variablized attribute example
match
  $n isa username "User";
  $u isa user, has username $n;
Example TypeDB Console output
   --------
    $n | User isa username
    $u | iid 0x1e00030000000000000005 isa user
   --------

To achieve the same result, we could also compare the attribute variable’s value with the used constant:

Specific users reading with variablized attribute and value comparison example
match
  $n isa username;
  $n == "User";
  $u isa user, has username $n;
Example TypeDB Console output
   --------
    $n | User isa username
    $u | iid 0x1e00030000000000000005 isa user
   --------

Alternatively, we could declare a value variable using the let statement, and integrate comparison into the owner instance declaration:

Specific users reading using value variables example
match
  let $n = "User";
  $u isa user, has username == $n;

Here, $n will be a value variable, not an attribute variable, so it’s cleaner to compare with it instead of binding from the previous examples. Try comparing the outputs of this and the previous examples.

Example TypeDB Console output
   --------
    $n | User
    $u | iid 0x1e00030000000000000005 isa user
   --------

Reading type labels

Types from database schemas are also available for match ing and retrieving. To bind a variable to a type, use type-related statements and variablize the respective side. The following example query retrieves the root types (types without supertypes) of the owners of attributes of type labelled email, using the isa statement, the label statement, and the sub! statement:

Matching types example
match
  $_ isa $owner-type, has $attribute-type $_;
  $attribute-type label email;
  not { $owner-type sub! $_; };
Example TypeDB Console output
   ---------------------
    $attribute-type | type email
    $owner-type     | type content
   ---------------------

Refer to the Match stage page for a detailed explanation and additional examples. A full list of statements, including the ones available for match ing, is available here.

Matching composite patterns

In the examples above we have already seen the use of not subpatterns. This pattern constructor, together with the disjunction constructor or, provide powerful ways to combine patterns.

Matching with disjunction example
match
  $u isa user;
  { $u has username "User"; } or { $u has email "alice@typedb.com"; };

Note that multiple semicolons are required: one semicolon for each single statement and a final semicolon to finalize the disjunction.

Example TypeDB Console output
   --------
    $u | iid 0x1e00030000000000000002 isa user
   --------
    $u | iid 0x1e00030000000000000005 isa user
   --------

Similarly, negations (logical "NOT") to reverse the declared constraint. The following example returns all user s without email s:

Matching with negation example
match
  $u isa user;
  not { $u has email $_; };
Example TypeDB Console output
   --------
    $u | iid 0x1e00030000000000000005 isa user
   --------

See Patterns for more details about patterns.

Pipelining stages

Let’s now discuss how to use the matched results in pipeline stages.

For example, it is frequently useful to get only a specific set of variables instead of everything that is matched in the constraint declarations. To select the necessary variables from the previous example, use the select operator:

Selecting variables example
match
  $a isa user, has username "Alice", has email "alice@typedb.com";
  $b isa user, has username "Bob", has email "bob@typedb.com";
  $f isa friendship, links (friend: $a, friend: $b);
select $f; # or reversed: "select $a, $b;"
Example TypeDB Console output
   --------
    $f | iid 0x1f00020000000000000000 isa friendship
   --------

To sort the outputs by chosen variables, use the sort operator:

Sorting results in descending order example
match
 $u isa user, has username $n;
sort $n desc;
Example TypeDB Console output
   --------
    $n | User isa username
    $u | iid 0x1e00030000000000000005 isa user
   --------
    $n | Charlie isa username
    $u | iid 0x1e00030000000000000004 isa user
   --------
    $n | Bob isa username
    $u | iid 0x1e00030000000000000003 isa user
   --------
    $n | Alice isa username
    $u | iid 0x1e00030000000000000002 isa user
   --------

As the mentioned operators can reuse the variables from previous pipeline stages and produce their own results, it is a common approach to combine multiple stages in complex pipelines. Continue exploring pipelines in the Complex pipelines manual.

The above example only cover simple 2-stage pipelines; in theory, there are now boundaries set for how you may combine stages. See Complex pipelines for more examples.

Converting output to JSON documents

Results of read pipeline stages in TypeDB can be converted into documents of the JSON format using fetch. The main purpose is to export the data in a format that’s easily readable by other appplications. But fetch not only allows you to flexibly structure your output based on variables, but also to perform subquerying operations, function calls, reference instance’s attributes, and much more.

You can use fetch after any stage producing rows, even if you insert or delete data.

Fetch receives a number of input rows and converts each row into a document of the provided structure, adding more requested information based on the query.

For example, you can simply label all the matched results and address their attributes (if applicable):

Creating a document of users example
match
  $u isa user, has username $n;
fetch {
  "user email": $u.email,
  "matched username": $n
};
Example TypeDB Console output
{
    "matched username": "Alice",
    "user email": "alice@typedb.com"
}
{
    "matched username": "Bob",
    "user email": "bob@typedb.com"
}
{
    "matched username": "Charlie",
    "user email": "charlie@typedb.com"
}
{
    "matched username": "User",
    "user email": null
}

Note the body of the fetch stage “declares” the structure of the output documents.

The example above shows the difference between matching using attributes and accessing attributes. Notice that the output of the previous fetch query contains null: it happens because the first match stage returned all user s with username s, and fetch had to process every incoming row without changing the number of answers, even if the request cannot be satisfied.

However, the query execution would return an error if the fetch stage was completely unsatisfiable. To achieve this, you can try accessing attributes of an attribute.

The following is an example of document nesting and JSON list usage. We also produce an additional document containing every existing attribute of the instance using .*.

Creating a document of users with nesting and lists example
match
  $u isa user;
fetch {
  "user information": {
    "username": $u.username,
    "emails": [ $u.email ]
  },
  "all user attributes": { $u.* }
};
Example TypeDB Console output
{
    "all user attributes": {
        "email": "alice@typedb.com",
        "username": "Alice"
    },
    "user information": {
        "emails": [ "alice@typedb.com" ],
        "username": "Alice"
    }
}
{
    "all user attributes": {
        "email": "bob@typedb.com",
        "username": "Bob"
    },
    "user information": {
        "emails": [ "bob@typedb.com" ],
        "username": "Bob"
    }
}
{
    "all user attributes": {
        "email": "charlie@typedb.com",
        "username": "Charlie"
    },
    "user information": {
        "emails": [ "charlie@typedb.com" ],
        "username": "Charlie"
    }
}
{
    "all user attributes": { "username": "User" },
    "user information": {
        "emails": [  ],
        "username": "User"
    }
}

Note how the missing email is represented as an empty list: wrapping empty results in lists produces empty lists [ ] instead of JSON nulls.

The .* command matches only existing attributes, thus, does not mention null s.

Refer to Fetch stage to explore all applications of fetch, its syntax, and more examples with subqueries, functions, and much more.

Having troubles?

Refer to the Debugging queries page for common debugging tips.