Queries
The ObjectBox Swift database supports building Queries using a query builder. Learn all about Query syntax and how to get to entities and their property values by filtering for specific criteria.
Building Queries that Return Entities
Beyond simple "fetch all" commands, like personBox.all()
, you may want to filter entities by their properties. ObjectBox's query syntax should be familiar to every Swift developer, because queries are written in Swift:
You simply write your class name, the property name, and then an operator, or a method name (like startsWith()
here). While most of these calls look like the regular Swift calls you're used to, they are actually provided by ObjectBox's code generator, so not all methods you would e.g. find on a String
are available for queries. See below for available operations.
Store query objects for re-use to increase performance.
You can call Query.find()
, Query.count()
, and all the other operations that execute a query multiple times. In performance-critical situations, building a new query object thousands of times can easily become costly.
Query Syntax
Combine Conditions with logical Operators and Parentheses
Query conditions can be combined by the use of the logical operators &&
and ||
.
Since these are overloads of native Swift operators, their standard operator precedence rules apply (&& > ||
). You can use parentheses to group conditions and affect the truth condition of the whole expression.
That's fancy talk for: group condition parts by wrapping them in parens.
QueryCondition Operators
ObjectBox also provides operators for the most common operations. Please note that the property comes first:
Collection containment:
∈
and∉
; for example, disallowing teens entry to your disco:Person.age ∉ (10..<18)
Equality:
==
and!=
, as inPerson.firstName == "Steve"
Comparison:
<
and>
, as inCocoaPod.rating > 4.5
Those operators are available for optional types, which currently lack the long form.
There are no custom operators for conditions like Property<T, String>.startsWith()
, as their names are already much more familiar to Swift developers.
Query Operations
Once you have a query set up properly, you can execute it as often as you want to get the latest results of its evaluation. These are the most common operations:
Query.find()
will return all results that matchQuery.count()
will return the count of all results that matchQuery.findUnique()
will return an unique match from the result set; returns nil if no result was found and throws if the result is not unique
Please refer to the Query API docs for a comprehensive list of permitted operations.
Sorting
In addition to specifying conditions you can order the returned results using the ordered()
method:
You can also pass flags to ordered(by:,flags:)
to sort in descending order, to sort case sensitively or to treat null values specially . For example, to sort the above results in descending order and case sensitively instead:
Order conditions can also be chained. Check the method documentation for details.
Modifying Conditions Later
You can modify conditions after creation of the query. That can be useful if your base query stays the same but one small part changes between find()
executions and you don't want to create a new Query object every time.
To do that, you use the query's setParameter()
methods. Refer to the Query API docs for a comprenhesive list of permitted setParameter()
variants.
setParameter Changes the Condition Value
There are many useful variants of the Query.setParameter()
and Query.setParameters()
method. (Note the plural s.) The first version will set the comparison value of a condition that matches to a new value. The second version does the same for comparisons with two values, hence the plural s.
For example:
query.setParameter(Person.age, to: 18)
for a query you built usingpersonBox.query { Person.age == 21 }.build()
will effectively change the condition toPerson.age == 18
query.setParameters(Person.age, to: 10, and: 18)
for a query you built usingpersonBox.query { Person.age.isBetween(0, and: 99) }.build()
will effectively change the condition toPerson.age.isBetween(10, and: 18)
You will get fatal errors if you try to call the plural-s-Version when the original condition only has one comparison value, and vice versa. Also note that this is an usage error and thus does not throw.
Use PropertyAlias to Disambiguate Conditions
One problem of the property-based approach above is that you have no control what happens when there are two or more conditions on the same property:
To resolve this ambiguity, you can register a short name (called an alias) for a query condition. That's a simple string to give the condition a name.
We use a variant of the definition operator (.=
) for this:
There is, of course, also a plural s-variant for conditions with two values. The same warning as above applies: make sure not to mix up the plural and singular versions.
A downside of the string-based alias approach is that you lose type information: you have to make sure not to pass a Double
to a setter for a condition that operates on String
, for example.
Building Queries that Operate on Properties
In addition to filtering entities, you can also build queries for their properties. These are aggregate functions like max
, min
, average
, count
, but also various find methods. These are available on the PropertyQuery
type, that you can get from a regular query like this:
The PropertyQuery
will respect all conditions of its original Query
. So in the example above, only entities with a firstName
that starts with "S" will be regarded. Use the empty block variant personBox.query()
if you want to operate on all entities.
Please refer to the PropertyQuery API docs for an exhaustive list of operations.
Currently, property queries do not honor the ordered(by:,flags:)
setting. If you need a fixed order for your proerty query results, you must sort them manually after retrieving them.
Aggregate Functions
A simple aggregate function is to request the maximum, minimum, or average value of a given property.
Number-based operations are only available for number-based properties and not for strings, for example. Also note that the average of a property is always a floating point number, even if the property is an Int
(what would you do with a non-fractional average anyway?).
Fetching all Property Values
In addition to aggregate functions, you can also use "find" methods. These will return any or all of the values of entities matching the query:
While the possibilities are not literally endless, they are plentiful if you combine query conditions with property-based requests.
Distinct and Unique Results
The property query can also only return distinct values, not producing duplicates. That means if you have 5 people in your database with ages [25, 30, 30, 40, 40]
, you will get [25, 30, 40]
as a distinct result set.
For example:
If only a single value is expected to be returned the query can be configured to throw if that is not the case:
You can combine distinct()
with findUnique()
.
Building Queries Traversing Relations
If your entity contains a relation to other entities, you can apply additional criteria to the related entities by using the link(_ property:, conditions:)
method on your query.
E.g. if you had an entity Order
that has a property ToOne<Order, Customer>
pointing to the customer that placed the order, you could select all orders belonging to a particular customer from today's orders using
This is equivalent to what other databases call a join
.
How do queries work?
Let's assume we have this interesting Entity:
For this entity, the code generator will create something like this:
That's your entity property metadata. The code generator will create these static properties for you with the same name as a stored object's properties. This is what you use for queries.
The associated types of the generic Property<Entity, Value, ReferencedType>
itself encode so much interesting information already to make the query interface very intuitive, for example:
Property<E, String, R>
exposes methods that make sense for strings, likecontains
Property<E, Int, R>
exposes methods that make sense for numbers, including comparisonsProperty<E, Date, R>
exposes methods for dates, likeisBetween(_:and:)
All of these factory methods create a QueryCondition
, the type used for queries, and a lot of them have intuitive operator variants. Please refer to the API docs for a full list of operators and methods.
Last updated