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 |
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:
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):
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
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:
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:
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
|
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:
match
$_ isa user, has username $n;
Equivalently, we could query Matching all usernames example
(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:
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:
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:
match
let $n = "User";
$u isa user, has username == $n;
Here, |
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:
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.
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:
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:
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:
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.
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):
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 However, the query execution would return an error if the |
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 .*
.
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 The |
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.