Queries¶
What’s in this document?
Given a SOML schema, the Semantic Objects generate a powerful query language in the form of GraphQL Input Objects. These objects can be used as Field Arguments to filter, order, and limit field queries:
<field> (ID: [ID!], limit: PositiveInteger, offset: PositiveInteger,
orderBy: <fieldType>_OrderBy, where: <fieldType>_Where_Multi):
[<FieldType>]!
Such arguments are generated for:
- Each SOML object type, as a root query that allows you to find one or more objects of that type.
- Each multi-valued field of every object.
More precisely, the generated arguments depend slightly on the type of field (scalar or object):
<scalarField> (limit: PositiveInteger, offset: PositiveInteger,
orderBy: _OrderBy, where: <scalarType>_Where_Multi):
[<scalarType>]!
<objectField> (limit: PositiveInteger, offset: PositiveInteger, ID: [ID!],
orderBy: <Object>_OrderBy, where: <ObjectType>_Where_Multi):
[<ObjectType>]!
- All scalar fields share the same
enum _OrderBy
. - Object fields can be filtered by
ID
.
Example Schema¶
Assume the following simple SOML schema:
Film:
name: rdfs:label
props:
director: {max: inf}
episodeId: {range: integer}
desc:
kind: literal
label: Description
range: stringOrLangString
rdfProp: voc:desc
planet: {descr: "Planets which appear in the Film", max: inf, range: Planet}
Planet:
name: rdfs:label
props:
desc:
kind: literal
label: Description
range: stringOrLangString
rdfProp: voc:desc
climate:
diameter: {label: "Diameter in Km", range: integer}
film: {descr: "Films which the planet appeared in", max: inf, range: Film}
The following GraphQL schema is generated:
# business objects
type Film implements Nameable & Object {
id: ID
type(ID: [ID!], limit: PositiveInteger, offset: PositiveInteger, orderBy: _OrderBy, where: ID_Where_Multi): [ID]!
name: String
episodeId: Integer
desc(value: String_Where, lang: String): Literal
director(limit: PositiveInteger, offset: PositiveInteger, orderBy: _OrderBy, where: String_Where_Multi, lang: String): [String]!
planet(ID: [ID!], limit: PositiveInteger, offset: PositiveInteger, orderBy: Planet_OrderBy, where: Planet_Where_Multi, lang: String): [Planet]!
}
type Planet implements Nameable & Object {
id: ID
type(ID: [ID!], limit: PositiveInteger, offset: PositiveInteger, orderBy: _OrderBy, where: ID_Where_Multi): [ID]!
name: String
desc(value: String_Where, lang: String): Literal
climate: String
diameter: Integer
film(ID: [ID!], limit: PositiveInteger, offset: PositiveInteger, orderBy: Film_OrderBy, where: Film_Where_Multi, lang: String): [Film]!
}
# root queries
type Query {
object(ID: [ID!], limit: PositiveInteger, offset: PositiveInteger, orderBy: Object_OrderBy, where: Object_Where_Multi, lang: String): [Object!]
film (ID: [ID!], limit: PositiveInteger, offset: PositiveInteger, orderBy: Film_OrderBy, where: Film_Where_Multi, lang: String): [Film!]
planet(ID: [ID!], limit: PositiveInteger, offset: PositiveInteger, orderBy: Planet_OrderBy, where: Planet_Where_Multi, lang: String): [Planet!]
}
The generated input objects for order (<fieldType>_OrderBy
) and filter (<fieldType>_Where_Multi
) are described in further sections.
Limit and Offset¶
The limit
and offset
arguments allow you to take a slice of the total results for a root query or multi-value field and implement pagination.
(There is no way to find the total number of results without getting the last one.)
Performance Considerations¶
Important performance considerations to keep in mind:
limit
andwhere
are optional, so you can request all objects of a certain type, all values of a multi-value field, or even allobject
in the system.- This is potentially very expensive, so you should always provide
where
filters and/or limits. orderBy
on a large result set is always expensive (even withlimit
) because GraphDB needs to find all results and then sort them in memory.- Deeply nested GraphQL queries can also be very expensive, even if you provide reasonable limits on every level. You should ensure that the number of results per level does not grow in a geometric progression.
- The Semantic Objects include some Denial of Service safeguards that refuse overly complex queries (over 15 levels), limit the number of results (total 100k triples), and timeout queries that are too slow.
OrderBy¶
Ordering features (what you can order by):
- You can use any single-value field to order its parent (multi-valued) object.
- You can order by several fields by putting them into a dictionary. The dictionary order determines the majority of ordering. (Note: this slightly contravenes the GraphQL spec that uses ordered dicts for busness Objects but not for input dicts: “Input object should be […] an unordered map supplied by a variable. […] The result of coercion is an unordered map.”)
- You can order by a chain of nested fields, as soon as each one is single-valued.
- You can order a scalar array by its values.
The following example illustrates all these points (assuming altName
is a string multi-valued field):
query companiesByCountryCodeThenName {
company(orderBy: {country: {gn_countryCode: ASC}, name: ASC}) {
id name
country {
id name
altName(orderBy: ASC)
gn_countryCode
}
}
}
OrderBy Inputs¶
A specifies ordering by using order field specifications, which are nested Input Objects. To implement the features described above, we generate the following ordering arguments (Input Object types):
All scalars are ordered the same way, so they share
enum _OrderBy
that specifies the ordering direction (ASC/DESC
).Every single-value field of an object gets an ordering argument corresponding to its type.
Only single-value fields are orderable, as we cannot be sure which value to use for a multi-value field.
All objects are orderable because they have at least one orderable field:
id
.We use
_typename
with one underscore because two underscores are reserved for GraphQL Introspection:“The input field must not have a name which begins with the characters “__” (two underscores)”.
name
gets the same_OrderBy
. The field used asname
(e.g., forFilm
andPlanet
below that isrdfs:label
) does not get such an argument because it would be redundant.The built-in types Interface Object and Literal also get respective ordering Input types.
"Ordering of scalars"
enum _OrderBy {ASC DESC}
"Order fields of Object"
input Object_OrderBy {
id: _OrderBy
_typename: _OrderBy
}
"Order fields of Literal"
input Literal_OrderBy {
value: _OrderBy
type: _OrderBy
lang: _OrderBy
}
"Order fields of Film"
input Film_OrderBy {
id: _OrderBy
_typename: _OrderBy
name: _OrderBy
episodeId: _OrderBy
}
"Order fields of Planet"
input Planet_OrderBy {
id: _OrderBy
_typename: _OrderBy
name: _OrderBy
climate: _OrderBy
diameter: _OrderBy
}
You could get all objects in the system ordered by _typename
then id
with a query like this.
(But that would be a very bad idea, see Performance Considerations.)
query AllObjects(orderBy: {_typename: ASC, id: DESC}) {
id
__typename
}
Scalar Ordering¶
Here are some details about how scalars are ordered.
These details come from the GraphDB SPARQL query processor and you can check them with a query like the following (try also to change the direction to DESC()
):
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
prefix my: <http://example.org/>
base <http://example.org/>
select * {
values ?x {
"0001-01-01T00:00:00"^^xsd:dateTime "0001-01-01"^^xsd:date
"z" "1" "2"
"z"@en "z"@en-GB "z"@fr "1"@en "1"@en-GB "1"@fr
undef
002 2 002.000 2.0 "002.000"^^xsd:double "2.0"^^xsd:double
001 1 001.000 1.0 "001.000"^^xsd:double "1.0"^^xsd:double
"1"^^my:foo "1"^^my:bar "2"^^my:baz
<foo> <http://example.org/bar> my:baz <mailto:foo@example.org> <geo:42.68,23.21> <urn:uuid:1234> <urn:isbn:4567>
}
} order by ASC(?x)
Values are grouped by kind. The ordering of these groups is as follows in the ASC
(ascending) direction:
Null (undef): when the ordering field is null for some objects. Check the Null Handling section below for more information.
IRIs, ordered alphabetically (prefixed and relative IRIs are expanded), e.g.,:
geo:42.68,23.21 http://example.org/bar http://example.org/baz http://example.org/foo mailto:foo@example.org urn:isbn:4567 urn:uuid:1234
Numeric values ordered numerically: see Lexical vs Value Space for details. (Note: the Turtle/SPARQL shortcuts
002
and002.000
mean"002"^^xsd:integer
and"002.000"^^xsd:decimal
, respectively):"001"^^xsd:integer "1"^^xsd:integer "001.000"^^xsd:decimal "1.0"^^xsd:decimal "001.000"^^xsd:double "1.0"^^xsd:double "002"^^xsd:integer "2"^^xsd:integer "002.000"^^xsd:decimal "2.0"^^xsd:decimal "002.000"^^xsd:double "2.0"^^xsd:double
Dates
ordered chronologically:"000001-01-01"^^xsd:date "0001-01-01"^^xsd:date "0001-01-02"^^xsd:date
dateTimes
ordered chronologically (please note these are not comparable to dates):"0001-01-01T00:00:00"^^xsd:dateTime "000001-01-01T00:00:00"^^xsd:dateTime
Datatyped literals (other than numbers, dates, and dateTimes), ordered first by datatype then by value:
"1"^^my:bar "2"^^my:baz "1"^^my:foo
langStrings
are ordered only by value and ignore the language. Note the order is not guaranteed if multiple triples have the same value in multiple languages:"1"@en "1"@en-GB "1"@fr "z"@en "z"@en-GB "z"@fr
or:
"1"@fr "1"@en "1"@en-GB "z"@en-GB "z"@fr "z"@en
Plain
strings
:"1"^^xsd:string "2"^^xsd:string "z"^^xsd:string
The order is stable, i.e., equal values of the same kind are emitted in the same order as encountered.
If you use DESC
(descending), the order is reversed, so for example nulls come last. See Null Handling for more information.
However, stability is preserved, which means that ASC
and DESC
are not complete inverses of each other.
Other SPARQL implementations may use different ordering between the groups. GitHub issue sparql-12#88 may lead to standardization of ordering in SPARQL 1.2.
Null Handling¶
While in SPARQL undefined values (undef) are placed in the results based on the ordering directions, the Semantic Objects always emit them at the end. This ensures that the relevant results are always retrieved first, helping to build UI components that would never have an empty first result page.
Note
The Semantic Objects alway emits the NULL
values at the end, regardless of the sorting direction.
ID Filtering¶
To find objects or subobjects by IRI, you can use the argument ID
.
It is a convenient shortcut, as ID: ...
means the same as id: {IN: ...}
.
It takes single or multiple IRIs as argument, represented as strings.
(You can omit the brackets around a singleton array value.)
Some examples (for brevity, we will use values like "foo" "bar"
that are not really valid IRIs):
Object by fixed IRI(s)
{object (ID:"foo") {id name}} {object (ID:["foo","bar"]) {id name}}
Object by IRI and then accessing its subobject by IRI(s):
{object(ID:"foo") {id name subobject(ID:"bar") {id name}}} {object(ID:"foo") {id name subobject(ID:["bar","baz"]) {id name}}}
Object by fixed IRI(s) of a sub-object. Please note that this query is different from the previous one: it finds objects that have a sub-object with a given IRI.
{object (where: {subobject: {ID:"foo"}}) {id name}} {object (where: {subobject: {ID:["foo","bar"]}}) {id name}}
Object by fixed external IRI(s). These are scalar fields of type
iri
, so you need to use the comparison operators described in the following sections.{object (where: {websiteUrl: {IN:"foo"}}) {id name}} {object (where: {websiteUrl: {IN:["foo","bar"]}}) {id name}}
Where Filtering¶
For top-level queries and multi-valued fields, you can use the where
argument
that expresses a search/filter condition in a structured way (an Input Object similar to a JSON dictionary),
using field names (including nested fields), comparison operators (e.g., LT
) and Boolean operators (e.g., OR
).
This takes care of 80% of the simple cases (comparing a field to a constant, checking for existence, and combinations thereof), and offers query writing assistance and validation.
Our where
syntax is inspired by Hasura, a GraphQL implementation over PostgreSQL.
See Hasura’s query manual
and query API reference for more details.
Each where
field represents a filter clause.
- Each clause must be satisfied for the filter to match (implicit
AND
). - It is enough to find one instance of matching fields or nested fields (implicit
EXISTS
).
We first illustrate each comparison operator and logical connective with an example, then describe the Where Input objects (formal grammar), and finally give more extensive examples.
For our demonstration purposes here, we give many examples in one or two lines in order to fit in more; normally you would place each field and closing bracket on a separate line (this is what GraphiQL’s Prettify button does).
Comparison Operators¶
Clauses can reference an object field followed by a comparison operator or nested field access.
Comparison operators come in pairs:
EQ:NEQ
: equality/inequality.IN:NIN
: inside/outside of an array of values (put them in square brackets). Brackets around a single value are optional, so all the queries below do the same. However, for clarity you should useIN
for multiple values, andEQ
for single values:{object (where: {field: {EQ:3}}) {name}} {object (where: {field: {IN:[3]}}) {name}} {object (where: {field: {IN:3}}) {name}}
LT:GTE
andLTE:GT
: comparison of numbers, strings or dates (less-than, greater-than-or-equal, less-than-or-equal, greater-than)RE:NRE
andIRE:NIRE
: regular expression match or mismatch (N
) for strings or IRIs. TheI
variants make case-insensitive comparisons, using Unicode alphabet collation.Warning
Regex comparison is slow, so you should only use it on small result sets.
Please note that SPARQL logic is 3-valued:
true
,false
andundef
(in case the input field is missing), so it is possible for both of these to be unsatisfied:{object (where: {field: {EQ:3}}) {id name}} # looks for a value equal to 3 {object (where: {field: {NEQ:3}}) {id name}} # looks for a value different from 3
To access nested fields, nest them in further input objects:
query BulgarianCompanies { company (where: {country: {gn_countryCode: {EQ: "BG"}}}) { id name country {id name gn_countryCode} } }
The following table shows which comparisons are applicable to which scalar types:
Datatypes | Comparisons |
---|---|
iri | EQ NEQ IN NIN RE NRE IRE NIRE |
string | EQ NEQ IN NIN LT GT LTE GTE RE NRE IRE NIRE |
unsignedByte unsignedShort unsignedLong unsignedInt integer positiveInteger nonPositiveInteger negativeInteger nonNegativeInteger positiveFloat nonPositiveFloat negativeFloat nonNegativeFloat double decimal | EQ NEQ IN NIN LT GT LTE GTE |
date time dateTime year yearMonth | EQ NEQ IN NIN LT GT LTE GTE |
boolean | EQ NEQ IN NIN LT GT LTE GTE |
To compare literals and union datatypes (literal, langString
, stringOrLangString
, dateOrYearOrMonth
)
you need to access their value
subfield. You can then use all comparison operators.
Note
When filtering by dateTime, the following formats will be accepted:
(where: {dateTimeRegular: {GT: "2016-06-23T09:07:20"}})(where: {dateTimeRegular: {GT: "2016-06-23T09:07:20Z"}})
The following would not be accepted:
(where:dateTimeRegular:{GT: "2016-06-23T09:07:20.000"}})
Logical Connectives¶
You can use the following logical connectives:
AND
(conjunction) is implicit between clauses in the same input objectTo compare two fields of the same object, just put them together in the same dict. E.g., search for people by birth date and name (Note: commas are optional in GraphQL):
{person (where: {birthDate: {EQ:"2010-01-01"}, name: {IRE:"smith"}}) {name}}
To apply two comparisons to one field (e.g., a range check), just put them together in the same dict. E.g., search for people born between 2010 and 2015 (exclusive). Note: this compares
birthDate
directly, so it assumes a declaration likebirthDate: {range: date}
(and not a literal):{person (where: {birthDate: {GTE:"2010-01-01", LT:"2015-01-01"}}) {name}}
AND
is an explicit conjunction. You will need to use it in two cases:To check two different instances of the same field. For example, look for people who have siblings born on two different dates:
{person (where: {AND: [{sibling: {birthDate: {EQ:"2010-01-01"}}}, {sibling: {birthDate: {EQ:"2015-01-01"}}}]}) { name sibling {name birthDate}}}
You cannot write this query using
IN
because that would look for one sibling born on any of the dates:{person (where: {sibling: {birthDate: {IN: ["2010-01-01","2015-01-01"}}}]) { name sibling {name birthDate}}}
However, if the sibling field being checked has multiple values or can match several times, such a check could still match a single node. The example query below will match a person with a single sibling named “John-Jane”:
{person (where: {AND: [{sibling: {name: {IRE: "john"}}}, {sibling: {name: {IRE: "jane"}}}]}) { name sibling {name}}}
To make two comparisons on one field using the same operator. (Note: this is only needed for the string operators
RE IRE NRE NIRE
.)Imagine you need to check for two words, “cheese” and “ham”, in any order. If you use
AND
at the outer level, there is no guarantee the same “title” value will be used:{article: (where: {AND: [{title: {IRE: "cheese"}}, {title: {IRE: "ham"}}]}) { title}}
So it is better to make the conjunction at the operator level:
{article: (where: {title: {AND: [{IRE: "cheese"}, {IRE: "ham"}]}}) { title}}
You could also do it with a single more expensive regex:
{article: (where: {title: {IRE: "cheese.*ham|ham.*cheese"}}) { title}}
OR
takes an array of clauses and applies a disjunction.For example, look for people named “john” (first name) or “smith” (last name):
{person (where: {OR: [{firstName: {IRE: "john"}}, {lastName: {IRE: "smith"}}]}) { firstName lastName}}
If you need to check for one of several values in the same field, it is easier to use
RE
with|
(for text comparison) orIN
(for fixed values). For example, look for “smith” or “jones” in last name:{person (where: {lastName: {IRE: "smith|jones"}}) { lastName}}
Look for a person born on one of two dates:
{person (where: {birthDate: {IN: ["2010-01-01","2015-01-01"]}}) { name birthDate}}}
You can use
OR
at the operator level if you need a disjunction of two comparisons on the same field. This query looks for articles with rating (a decimal number) outside the range 1..3:{article (where: {rating: {OR: [{LT:1} {GT:3}]}}) { title rating}}
NOT
negates a clause. Most of the time you will not need it because you can use negation at the operator level. But you may need it for some complicated tests.EXISTS
is implicit when checking fields and sub-fields (one value is enough to satisfy the condition).ALL
is applicable to multi-valued fields and checks that all values satisfy the condition. For example, find people all of whose siblings are young babies (as of 2019):{person (where: {ALL: {sibling: {birthDate: {GTE: "2019-01-01"}}}}) { name sibling {name birthDate}}}
Note that a person without any siblings will also be returned by the above query (a vacuous truth), so you may want to add an existence check:
{person (where: {ALL: {sibling: {birthDate: {GTE: "2019-01-01"}}} sibling: {birthDate: {GTE: "2019-01-01"}}}) { name sibling {name birthDate}}}
For such cases you can use the
ALL_EXISTS
shortcut. It is applicable to multi-valued fields and checks bothALL
andEXISTS
. The example query below finds people who have young baby siblings, and only such siblings:{person (where: {ALL_EXISTS: {sibling: {birthDate: {GTE: "2019-01-01"}}}}) { name sibling {name birthDate}}}
Please note that ALL
is expensive since it involves a SPARQL condition FILTER NOT EXISTS
.
We have implemented query optimizations that use the GraphDB cardinality statistics
(similar to what is printed in the Explain Plan)
to convert such clause to DISTINCT
or MINUS
in appropriate cases.
ALL_EXISTS
is even more expensive since it combines a FILTER NOT EXISTS
with FILTER EXISTS
Where Inputs¶
The definition of the where
“formal grammar” is in the form of Input Objects added to the generated GraphQL schema.
By using these definitions, your query development environment (GraphiQL, GraphQL Playground or similar)
should provide efficient assistance for writing where
queries.
Here is an example from Ontotext’s Star Wars API demo service:

The GraphiQL UI guides you with autocompletion to easily enter a query like this one:
{
starship(where: {cargoCapacity: {LT:100}}) {
id
name
cargoCapacity
}
}
For every GraphQL object type (including Literal
) we generate two input objects.
<Type>_Where
is used in contexts where the type appears in a single-value field.<Type>_Where_Multi
is used in contexts where the type appears in a mutli-value field. The difference is that this one has the connectivesALL
andALL_EXISTS
.- You can use logical connectives
AND OR NOT
to make compound comparisons using the same inputs. (The first two take an array of comparisons.) - Both inputs enumerate all object fields, both scalar and object.
- The prop mapped to the
name
characteristic (if any) is skipped since you can filter by fieldname
.
"<Type> comparisons, single-value fields"
input <Type>_Where {
"Check by id" ID: [ID!]
"Conjunction of <Type> comparisons" AND: [<Type>_Where!]
"Disjunction of <Type> comparisons" OR: [<Type>_Where!]
"Negation of <Type> comparison" NOT: <Type>_Where
# for each single-value field (scalar except "name", or object):
"<single_field> comparisons" single_field: <single_field_Type>_Where
# for each multi-value field (scalar, or object):
"<multi_field> comparisons" multi_field: <multi_field_Type>_Where_Multi
}
"<Type> comparisons, multi-value fields"
input <Type>_Where_Multi {
"Check by id" ID: [ID!]
"Conjunction of <Type> comparisons" AND: [<Type>_Where_Multi!]
"Disjunction of <Type> comparisons" OR: [<Type>_Where_Multi!]
"Negation of <Type> comparison" NOT: <Type>_Where_Multi
"ForAll for <Type>" ALL: <Type>_Where_Multi
"ForAll and Exists for <Type>" ALL_EXISTS: <Type>_Where_Multi
# for each single-value field (scalar except "name", or object):
"<single_field> comparisons" single_field: <single_field_Type>_Where
# for each multi-value field (scalar or object):
"<multi_field> comparisons" multi_field: <multi_field_Type>_Where_Multi
}
For every scalar type we also generate two input objects.
- Similarly to the above, one is for single-value fields, the other for multi-value fields.
- They also add the appropriate logical connectives.
NOT
is excluded at this level because you can use negation at the operator level (e.g.,NIN
is the negation ofIN
).- They enumerate the appropriate comparison operators for that scalar. For example,
Int
andDate
have the following comparisons:
"Int comparisons, single-value fields"
input Int_Where {
"Equal to Int" EQ: Int
"Not equal to Int" NEQ: Int
"In list of Int" IN: [Int!]
"Not in list of Int" NIN: [Int!]
"Less than Int" LT: Int
"Less or equal to Int" LTE: Int
"Greater than Int" GT: Int
"Greater or equal to Int" GTE: Int
"Conjunction of Int comparisons" AND: [Int_Where!]
"Disjunction of Int comparisons" OR: [Int_Where!]
}
"Int comparisons, multi-value fields"
input Int_Where_Multi {
"Equal to Int" EQ: Int
"Not equal to Int" NEQ: Int
"In list of Int" IN: [Int!]
"Not in list of Int" NIN: [Int!]
"Less than Int" LT: Int
"Less or equal to Int" LTE: Int
"Greater than Int" GT: Int
"Greater or equal to Int" GTE: Int
"Conjunction of Int comparisons" AND: [Int_Where_Multi!]
"Disjunction of Int comparisons" OR: [Int_Where_Multi!]
"ForAll for Int" ALL: Int_Where_Multi
"All and Exists for Int" ALL_EXISTS: Int_Where_Multi
}
- Finally,
String
andID
have the additional comparison operatorsRE IRE NRE NIRE
:
"String comparisons, single-value fields"
input String_Where {
"Equal to String" EQ: String
"Not equal to String" NEQ: String
"In list of String" IN: [String!]
"Not in list of String" NIN: [String!]
"Less than String" LT: String
"Less or equal to String" LTE: String
"Greater than String" GT: String
"Greater or equal to String" GTE: String
"Regex match" RE: String
"Case-insensitive regex match" IRE: String
"Regex mismatch" NRE: String
"Case-insensitive regex mismatch" NIRE: String
"Conjunction of String comparisons" AND: [String_Where!]
"Disjunction of String comparisons" OR: [String_Where!]
}
"String comparisons, single-value fields"
input String_Where_Multi {
"Equal to String" EQ: String
"Not equal to String" NEQ: String
"In list of String" IN: [String!]
"Not in list of String" NIN: [String!]
"Less than String" LT: String
"Less or equal to String" LTE: String
"Greater than String" GT: String
"Greater or equal to String" GTE: String
"Regex match" RE: String
"Case-insensitive regex match" IRE: String
"Regex mismatch" NRE: String
"Case-insensitive regex mismatch" NIRE: String
"Conjunction of String comparisons" AND: [String_Where_Multi!]
"Disjunction of String comparisons" OR: [String_Where_Multi!]
"ForAll for String" ALL: String_Where_Multi
"All and Exists for String" ALL_EXISTS: String_Where_Multi
}
Where Examples: Persons and Articles¶
Now that you are familiar with the grammar of where
queries, let’s look at more complex examples.
We start with some examples about articles and persons.
- persons with some article with rating>=4, ordered by name
{person (where: {article: {rating: {GTE:4}}} orderBy: {name:ASC}) {
name}}
- same, but also return those articles, ordered by title
{person (where: {article: {rating: {GTE:4}}} orderBy: {name:ASC}) {
id name
article (where: {rating: {GTE:4}} orderBy: {title:ASC}) {
id title}}}
- person by name, and his articles
{person (where: {name: {EQ: "A.U.Thor"}}) {
id
article {id name}}}
- persons named “smith” (case insensitive)
{person (where: {name: {IRE:"smith"}}) {
id name}}
- articles whose titles start with Pizza (case-sensitive)
{article (where: {title: {RE:"^Pizza"}}) {
id title}}
- persons named “smith” who wrote any article with “pizza” in the title (case-insensitive)
{person (where: {name: {IRE:"smith"}
article: {title: {IRE:"pizza"}}}) {
name
article {title}}}
Where Examples: Star Wars¶
Below are some examples that you can try on Ontotext’s Star Wars API demo service. See the blog post A New Hope: The Rise of the Knowledge Graph (Navigating through the Star Wars universe with knowledge graphs, SPARQL and GraphQL) for an explanation of the data model.
You can compare these to similar queries at the GraphCool SWAPI.
- find ships with cargo capacity between 110 and 150 tons:
{starship (where: {cargoCapacity: {GTE:110, LTE:150}}) {
name cargoCapacity}}
- find ships that have only tall pilots (over 180cm), have at least one such pilot, and return all their pilots:
{starship (where: {ALL_EXISTS: {pilot: {height: {GT:180}}}}) {
name
pilot {name height}}}
- we can express this in a more verbose manner through double negation (for
ALL
) and repeating the clause (forEXISTS
). In the above case the Platform optimizes in the second clause: it looks forEXISTS pilot
but does not expand the condition further ({height: {GT:180}}
). This optimization is not possible if you write out the second clause in full.
{starship (where: {NOT: {pilot: {NOT: {height: {GT:180}}}}
pilot: {height: {GT:180}}}) {
name
pilot {name height}}}
- find ships that have pilots that also drive a vehicle whose name contains “a”, and return all those ships, pilots and vehicles. Unfortunately we need to repeat the same sub-condition in the ship, pilot and vehicle filters (unlike field fragments, there are no “filter fragments” to let us reuse filters):
{starship (where: {pilot: {vehicle: {name: {IRE: "a"}}}}) {name
pilot (where: {vehicle: {name: {IRE: "a"}}}) {name
vehicle (where: {name: {IRE: "a"}}) {name}}}}
- find ships that do not have tall pilots driving a vehicle whose name contains “a”, and return the ships, pilots and vehicles.
{starship (where: {NOT: {pilot: {height: {GT:180}
vehicle: {name: {IRE: "a"}}}}})
{name
pilot {name height
vehicle {name}}}}
Note that the last query returns:
- starships without any pilot (e.g., “Calamari Cruiser”)
- pilots that do not drive a vehicle (e.g., “Darth Vader” of “TIE Advanced x1”)
- pilots without any height (e.g., “Arvel Crynyd” of “A-wing”)
- tall pilots that drive a vehicle but it has no “a” in the name (e.g., “Obi-Wan Kenobi” who drives “Tribubble bongo”)
Where Examples: Companies¶
Query examples about companies:
- Bulgarian companies with any funding transaction. The empty dict
funding: {}
just checks for existence
{country (ID:"http://sws.geonames.org/732800/") {
company (where: {funding: {}}) {
id name}}}
- companies with a funding transaction of over $1M, and their three largest fundings
{company (where: {funding: {amount: {GT:1.0}}}) {
id name
funding (where: {amount: {GT:1.0}, orderBy: {amount:DESC}, limit:3}) {
date amount}}}
- companies founded between 2015 and 2019 (exclusive)
{company (limit: 100, where: {foundedOn: {GTE:"2015-01-01", LT:"2019-01-01"}}) {
id name foundedOn}}
- companies located in cities within a geo bounding box (between Pazardzhik and Veliko Tarnovo in Bulgaria)
{company (where: {city: {wgs_lat: {GTE:42.384992, LTE:43.009443},
wgs_long: {GTE:24.353453, LTE:25.613992}}}) {
id name
city {name}}}
- companies of two given Bulgarian legal types, limited to 100
{company (limit: 100,
where: {cg_type: {skos_notation: {IN: ["BG:EAD", "BG:AD"]}}}) {
id name
type {name skos_notation}}}
The following queries find funding transactions targeting a Bulgarian company (in our model each transaction has only one target company):
- starting from the country
{country (ID: "http://sws.geonames.org/732800/") {
company (where: {funding: {}}) {
name
funding {amount date}}}}
- starting from the company
{company (where: {funding: {}
country: {ID: "http://sws.geonames.org/732800/"}}) {
name
funding {amount date}}}
- starting from the funding
{transaction (where: {company: {country: {ID: "http://sws.geonames.org/732800/"}}}) {
amount date
company {name}}}
Scalar Comparisons¶
Scalars are compared according to their value (and not their lexical string), using the same logic illustrated in Scalar Ordering.
For example, "2"^^xsd:integer = "002.000"^^xsd:double
.
To ensure this, we implement EQ
using SPARQL IN (...)
rather than a direct value pattern.
IN
or =
is a bit slower than a direct value pattern, but the GraphDB literal index makes it fast enough.
We also normalize scalar values on output, so "002.000"^^xsd:double
stored in GraphDB will be returned as "2"^^xsd:double
.
While numerical values are comparable across datatypes, the normalization never changes the datatype.
Language Preference for langString Properties¶
The lang
argument can be used to pass the preferred language(s) to be fetched when selecting langString
or stringOrLangString
properties. The argument is applied to each generated query and on each complex property selection. This allows changing the default languages at each query level if needed.
The following example illustrates this:
query films { film(lang: "en,fr") { id name desc { value lang } planet(lang: "ALL") { id name desc { value lang } } } }
For this query, we have defined that we want to retrieve the only English or French language value from the multi-language properties, and at planet
property level we want all values for the multi-valued sub-properties.
For more details on the lang
argument format and
how to use it, check the Filtering Literal Values tutorial.