FlureeQL Query Syntax
FlureeQL is a JSON-based query language for retrieving Fluree data. It is modeled as a JSON-representation of the W3C SPARQL standard.
Query Object
FlureeQL queries are JSON with the following keys:
key | required? | type |
---|---|---|
@context | no | object |
from | yes | string or array |
where | no | array or object |
select | yes | array or object |
t | no | string or integer |
groupBy | no | array or string |
having | no | array or string |
orderBy | no | string |
Examples
Basic query example
{ "from": "cookbook/base", "where": { "@id": "?s", "bestFriend": "?friend" }, "select": { "?s": [ "*", { "bestFriend": [ "*" ] } ] },}
@context
required? | type |
---|---|
no | object |
Understanding the use of @context
within the W3C JSON-LD standard can be important for using it propertly within queries. For an explanation of how to use @context
in FlureeQL, consider first reading our Guides page on Working with Context.
Examples
Example of a Query without @context
{ "from": "cookbook/base", "where": { "@id": "?s", "@type": "http://example.org/Person" }, "select": { "?s": ["http://schema.org/name"] }}
Example of a Query with @context
{ "@context": { "schema": "http://schema.org/", "ex": "http://example.org/" }, "from": "cookbook/base", "where": { "@id": "?s", "@type": "ex:Person" }, "select": { "?s": ["schema:name"] }}
from
required? | type |
---|---|
yes | string |
The from
clause specifies the ledger to query. It takes the same value that would have been supplied to the /create
endpoint when the ledger was created.
If I named a ledger "cookbook/base"
when I created it, then when I queried it, I would use "from": "cookbook/base"
to identify that this query should target that particular ledger.
Examples
Querying a ledger named "cookbook/base"
{ "@context": { "schema": "http://schema.org/", "ex": "http://example.org/" }, "from": "cookbook/base", "where": { "@id": "?s", "@type": "ex:Person" }, "select": { "?s": ["schema:name"] }}
select
required? | type |
---|---|
yes | array or object |
A select can be either a select object or a select array.
Select Object
With a select object, each key is a logic variable name and the value is an array of select expressions.
The select clause determines what objects are returned in query results. Each logic variable corresponds to a set of subjects, and a JSON object gets constructed for each subject. The array of select expressions describes how to build these JSON objects by specifying what predicates to include.
Select Object Examples
Simple select
example
{ "select": { "?s": [ "name", { "bestFriend": ["*"] } ] }, "where": { "@id": "?s", "bestFriend": "?friend" }}
Select Array
With a select array, each element is a logic variable name or a select object. When your select clause is a select array, each element of the query results is an array.
Select Array Examples
Logic variable
{ "select": ["?s", "?name", "?friend"], "where": { "@id": "?s", "bestFriend": "?friend", "name": "?name" }}
Logic variable and a select object
{ "select": [ { "?s": ["*"] }, { "?friend": ["*"] } ], "where": { "@id": "?s", "bestFriend": "?friend", "name": "?name" }}
Logic variable name
Logic variables are placeholders for sets of subjects. They are identified by logic variable names.
Logic variable names are strings that begin with a question mark, ?
, followed
by alphanumeric characters, hyphens, and underscores.
See the tutorial section on queries for information about logic variables.
Examples
"?firstname""?first-name""?first_name""?address-1"
Select expression
Select clause objects map logic variables to select expressions. In the
select clause below, "?s"
identifies a logic variable and ["name", { "bestFriend": ["*"] }]
is a select expression.
{ "select": { "?s": [ "name", { "bestFriend": ["*"] } ] }, "where": { ... }}
Select expressions describe the shape of data to be returned for subjects. It specifies what predicates to include in the result object, and how to traverse predicates to return nested values.
A select expression can be one of:
- A predicate, like
"schema:name"
. When included, the corresponding value is returned in the result object. - The wildcard symbol,
"*"
. When included, all of a subject's predicates are included in its result object. - A node object template. This describes how to traverse nested predicate valeus.
Examples
// wildcard"*"// predicate"schema:name"// node object template; see below{ "schema:address": ["*"]}
Node object template
Node object templates are objects where the keys are predicates, and the values are arrays of select expressions. Node object templates express for the given predicate, construct an object that contains the predicates described by the array of select expressions.
{ predicate: [select-expression, select-expression, ...], predicate: [select-expression, select-expression, ...], ...}
Examples
Return an object that has all predicates for the node that "bestFriend
refers to
{ "select": { "?s": [ { "bestFriend": ["*"] } ] }, "where": { ... }}
Multi-level nested object
{ "select": { "?s": [ { "bestFriend": [ { "address": [ "*" ] } ] } ] }, "where": { ... }}
where
required? | type |
---|---|
no | array or object |
You can think of a FlureeQL query executing in two phases: the where
phase and
the select
phase. The where
phase filters and returns sets of bound values that the select
phase can use to construct JSON objects. The where
clause may return node subject ?bindings
that we then use with select
expressions like "*"
to perform graph crawls from those subject nodes.
The where
clause may also retrieve all the bindings we need, and we simply instruct the select
clause that we want those bindings returned directly as query results.
In any case, in addition to establishing logic variables for bound values, we use the where
clause to establish various data constraints for the data we are interested in querying (e.g. filtering by a particular predicate value, or expressing optional and outer-join data conditions).
When a where
clause is an array, it can combine a series of where conditions and where
operations.
Where condition
A where condition describes relationships between nodes that must be satisfied for the nodes to be included in result sets, and it names those sets.
For example, in this where condition:
{ "@id": "?s", "schema:name": "Sheil"}
a set of subjects that have a "schema:name"
equal to "Sheil"
is bound
to the logic variable named "?s"
.
In this where condition:
{ "@id": "?s", "schema:name": "?name"}
two sets become available to select
:
"?s"
is the set of nodes that have any data on the predicate,"schema:name"
"?name"
is the set of value nodes that the"schema:name"
predicate points to
Multiple where conditions are implicitly joined with a logical AND. For example, given these two where conditions:
{ "@id": "?s", "schema:name": "Freddie", "schema:familyName": "Mercury"}
the set "?s"
would include all nodes that have both a "schema:name"
of "Freddie"
and a "schema:familyName"
of "Mercury"
.
A node that satisfies ONE of those conditions but not BOTH would NOT be bound to ?s
Successive where
conditions
A where
clause can express multiple, successive where
conditions as either an array of objects or as nested, related objects.
The following two examples are effectively equivalent:
{ "where": [ { "@id": "?s", "bestFriend": "?friend" }, { "@id": "?friend", "schema:name": "?name" } ]}
{ "where": { "@id": "?s", "bestFriend": { "@id": "?friend", "schema:name": "?name" } }}
In the second example above, because the conditions on ?s
and ?friend
are nested in a single, hierarchical JSON object, we could even remove the "@id": "?friend"
line, if we didn't need to use it in the select
statement. We need it in the first example to relate the two nodes to one another, but in the second example, we could have just as easily used:
{ "where": { "@id": "?s", "bestFriend": { "schema:name": "?name" } }}
Finally, it is possible in where conditions to express a logic variable for a set of properties matching a particular condition. This is commonly done for one of two reasons:
- We have identified a subject IRI, and now want all the properties with object values on that entity, or
- We have an object value, and now we want all the properties that have that value.
Example of a where condition that uses a logic variable for a set of properties
{ "where": { "@id": "http://example.org/jack", "?p": "?o" }}
Where operations
Where operations allow you to express more complex behavior than where conditions. There are four such operations:
Where operations are represented as arrays, where the first element is the name of the operation, and the subsequent elements are arguments to the operation, for example:
...[ "optional", { "@id": "?s", "schema:name": "?name" }, { "@id": "?s", "schema:age": "?age" }],...
filter
The "filter"
operation lets you make comparisons beyond simple equality. For
example, if you wanted to select all people older than 45 and younger than 50,
you could write this:
{ "where": [ { "@id": "?s", "schema/age": "?age" }, ["filter", "(> ?age 45)", "(< ?age 50)"] ], "select": { "?s": ["*"] }}
Filter expressions
Filter expressions are strings that represent lisp-like expressions which can
use your logic variables to return boolean values. When you have multiple filter
expressions, an implicit AND
is used to join them.
Take this example:
["filter", "(> ?age 45)", "(< ?age 50)"]
With lisp, expressions are enclosed in parentheses. You can think of the first
element of the expression as a function, and the rest of the elements as
arguments. In the first filter expression, "(> ?age 45)"
, >
(greater than)
is the function, and the arguments are ?age
and 45
. In English this would
read as ?age
is greater than 45
.
Inside of each filter expression, you can refer to the logic variables that you've bound elsewhere in your where
clause (i.e. the way that we use ?age
as a variable in the example above).
Click here for a list of valid functions you can use in filter
expressions.
union
The "union"
operation allows you to express boolean OR logic (or in query terms, perform a full outer join). Take this query:
{ "where": { "@id": "?s", "@type": "ex:Person", "schema:email": "?email", "ex:email": "?email" }, ...}
In the query above, if an entity does not have BOTH a value on schema:email
and a value on ex:email
AND if those two values are not identical (because we express ?email
for both values), then that entity will not be bound to ?s
.
But we can easily imagine a scenario where we know some entities have a value on schema:email
and some have a value on ex:email
, and regardless of which one they have, we want to return them in our query results.
This is where we can use union
to express a logical OR condition (aka perform a full outer join on values of those two properties):
{ "@context": { "schema": "http://schema.org/", "ex": "http://example.org/" }, "where": [ { "@id": "?s", "@type": "ex:Person" }, [ "union", { "@id": "?s", "schema:email": "?email" }, { "@id": "?s", "ex:email": "?email" } ] ], "select": ["?s", "?email"]}
In English, this query reads:
- Return all subjects
"?s"
such that: - the subject has an
@type
(i.e."rdf/type"
) of"ex:Person"
- AND the subject
- EITHER has the property
"schema:email"
(bind its value to"?email"
) - OR has the property
"ex:email"
(also bind its value to"?email"
) - OR has both properties
- EITHER has the property
bind
The "bind"
operation allows you to bind a logic variable to a value. This can be useful if wanting to set a binding to the return value of a functional operation. We can use the same functions that we used in our filter
expressions above.
Like filter
and union
, bind
is an array where the first element is the name of the operation. In the case of bind
, the following pairs of elements are the logic variable to bind and the expression to evaluate.
["bind", "?canVote", "(>= ?age 18)"]
Example of a where operation that uses bind
{ "@context": { "schema": "http://schema.org/" }, "where": [ { "@id": "?s", "schema:age": "?age" }, ["bind", "?canVote", "(>= ?age 18)"] ], "select": ["?s", "?age", "?canVote"]}
Click here for a list of valid functions you can use in bind
expressions.
optional
The "optional"
operation allows you to use logic variable bindings for values that may or may not exist. This is similar to a left outer join in SQL.
For example, if you wanted to select all people and their best friends, but if you didn't want the query results to implicitly filter out people who had no values on bestFriend
you could write this:
{ "@context": { "ex": "http://example.org/" }, "where": [ { "@id": "?s", "@type": "ex:Person" }, ["optional", { "@id": "?s", "bestFriend": "?friend" }] ], "select": ["?s", "?friend"]}
When the where
clause of the above query executes, ?s
will be bound to all entities of @type
, ex:Person
. If we had NOT used optional
on the next line, then any ?s
entities with no value on bestFriend
would be implicitly dropped from the ?s
result set. But, because we used optional
, those entities will be retained in the ?s
result set, and ?friend
will be bound to null
for those entities.
optional
, like filter
, union
, and bind
, is an array where the first element is the name of the operation. In the case of optional
, the following elements are the where conditions to evaluate.
NOTE: There is a difference between the following two
optional
operations:
["optional", { "@id": "?s", "bestFriend": "?friend"}, { "@id": "?s", "name": "?name"}]
["optional", { "@id": "?s", "bestFriend": "?friend", "name": "?name"}]
In #1, an entity could have a value on either
bestFriend
orname
, and that value would then be bound to?friend
or?name
, respectively (the other would benull
).In #2, if an entity had only a value on either
bestFriend
orname
, then BOTH?friend
and?name
would be bound tonull
.
If you are used to SQL datbases and queries, you might be confused why we call this operation optional
. In SQL, it is possible to persist null
values on a column for a particular row.
It is important to remember that JSON-LD expresses a serialization of RDF triples. In the world of RDF triples, there are not null
values. If a subject does not have a value on a particular predicate, then that subject simply does not have a triple with that predicate--or said differently, the triple simply does not exist.
When we use optional
, we are saying "if this triple exists, then bind the value to the logic variable, otherwise bind null
to the logic variable."
Valid Functions for Use in Filter and Bind Expressions
function | description | example |
---|---|---|
and | logical AND (same as && ) | (and ?isRegistered ?canVote) |
&& | logical AND (same as and ) | (&& ?isRegistered ?canVote) |
|| | logical OR | (|| ?isAnAdult ?canVote) |
! | logical NOT (same as not ) | (! (strStarts ?url \"http\")) |
not | logical NOT (same as ! ) | (not (strStarts ?url \"http\")) |
= | equal | (= ?age 45) |
not= | not equal | (not= ?age 45) |
> | greater than | (> ?age 45) |
>= | greater than or equal | (>= ?age 45) |
< | less than | (< ?age 45) |
<= | less than or equal | (<= ?age 45) |
+ | addition | (< (+ ?age 10) 45) |
- | subtraction | (< (- ?age 10) 45) |
* | multiplication | (< (* ?age 2) 45) |
/ | division | (< (/ ?age 2) 45) |
quot | integer division | (< (quot ?age 2) 45) |
abs | absolute value | (> (abs ?age) 45) |
round | round to nearest integer | (> (round ?age) 45) |
ceil | round up to nearest integer | (> (ceil ?age) 45) |
floor | round down to nearest integer | (> (floor ?age) 45) |
bound | returns true if logic variable is bound | (bound ?age) |
coalesce | returns first non-null value | (> (coalesce ?age ?defaultAge) 45) |
nil? | returns true if value is null | (nil? ?age) |
re-pattern | returns a regex pattern, for use with re-find | (re-find (re-pattern \"^http\") ?url) |
re-find | returns true for regex matches | (re-find (re-pattern \"^http\") ?url) |
strStarts | returns true if string starts with substring | (strStarts ?url \"http\") |
strEnds | returns true if string ends with substring | (strEnds ?url \".com\") |
strLen | returns length of string | (> (strLen ?name) 5) |
contains | returns true if string contains substring | (contains ?name \"fred\") |
now | returns the current time | (> ?date (now)) |
if | conditional | (if (> ?nums 30) (> ?age 100) true) |
isNumeric | returns true if value is numeric | (isNumeric ?age) |
t
required? | type |
---|---|
no | string or integer |
The t
clause lets you specify a dateTime value or commit number to query against. We refer to this as a "time-traveling" query.
The t
clause can take two possible values, an ISO 8601 dateTime string (expressing a particular moment in time according to the database state), or a commit integer (expressing a particular relative commit number in the history of the ledger).
Note that, while a commit integer expresses an actual moment of data state change, an ISO dateTime string likely expresses a moment in-between commits. The use of an ISO dateTime string, then, expresses an interest in the data state as of that moment in time.
That is, if commit #1 took place at '2023-10-30T12:43:38.452Z'
and commit #2 took place 10 minutes later at '2023-10-30T12:53:38.452Z'
, then a query for a dateTime moment in the middle of those two commits, e.g. "t": '2023-10-30T12:48:38.452Z'
, would reflect the data state as of commit #1.
{ "@context": { "schema": "http://schema.org/", "ex": "http://example.org/" }, "where": { "@id": "?s", "@type": "ex:Yeti", "schema:age": "?age" }, "select": ["?s", "?age"], "t": "2023-10-30T12:38:45.389Z"}
Or the same query, but using a commit integer value for t
:
{ "@context": { "schema": "http://schema.org/", "ex": "http://example.org/" }, "where": { "@id": "?s", "@type": "ex:Yeti", "schema:age": "?age" }, "select": ["?s", "?age"], "t": 4}
groupBy
required? | type |
---|---|
no | array or string |
The groupBy
clause lets you group query results by a predicate and perform
aggregate operations on the groups.
For example, let's say you have a query that returns the name and company for all people in your database, and the results look like this:
[ { "ex:company": "Fluree", "ex:name": "Letitia" }, { "ex:company": "Fluree", "ex:name": "Freddy" }, { "ex:company": "Beep Boop", "ex:name": "Daniel" }]
You could group these results by ex:company
to see who works for each company:
{ "@context": { "ex": "http://example.org/" }, "select": ["?company", "?name"], "where": { "@id": "?s", "ex:name": "?name", "ex:company": "?company" }, "groupBy": "?company"}
This would return:
[ ["Fluree", ["Letitia", "Freddy"]], ["Beep Boop", ["Daniel"]]]
You can also perform aggregate operations on the grouped data. These operations let you answers questions like, "how many people are employed at each company?" or "what is the lowest price that a product has sold for?"
If you wanted to get a count of people per employer, you could write a query like this:
{ "@context": { "ex": "http://example.org/" }, "select": ["?company", "(count ?s)"], "where": { "@id": "?s", "ex:name": "?name", "ex:company": "?company" }, "groupBy": "?company"}
And you would get results like this:
[ ["Fluree", 2], ["Beep Boop", 1]]
having
required? | type |
---|---|
no | array or string |
Having lets you filter on grouped data using arbitrary expressions.
NOTE:
having
is only valid when used withgroupBy
.
{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "select": ["?name", "?favNums"], "where": { "@id": "?s", "schema:name": "?name", "ex:favNums": "?favNums" }, "groupBy": "?name", "having": "(>= (avg ?favNums) 10)"}
As English, this query would read: "Find all entities with names and favorite numbers. Group the results by name. Keep only results where the average of all ?favNums
for a ?name
is greater than or equal to 10
."
If we had used a
filter
function withoutgroupBy
, then we would have been filtering on the average of allfavNum
values across all entities. By using,groupBy
&having
, we can filter on the average of only those values grouped to a particular?name
.