Querying Data
TinyFlux’s query syntax will be familiar to users of popular ORM tools. It is similar to that of TinyDB, but TinyFlux contains four different query types, one for each of a point’s four attributes.
The query types are:
TimeQuery
for querying points bytime
.MeasurementQuery
for querying points bymeasurement
.TagQuery
for querying points bytags
.FieldQuery
for querying points byfields
.
For the remainder of this section, query examples will be illustrated with the .search()
method of a TinyFlux database. This is the most common way to query TinyFlux, and the method accepts a query and returns a list
of Point
objects matching the query. In addition, there are a handful of other database methods that take queries as argument and perform some sort of search. See the Exploring Data section for details.
Note
.search()
will return Points in sorted time order by default. To return points in insertion order, pass the sorted=False
argument, like: db.search(query, sorted=False)
.
Simple Queries
Examples of the four basic query types are below:
Measurement Queries
To query for a specific measurement, the right-hand side of the MeasurementQuery
should be a Python str
:
>>> from tinydb import MeasurementQuery
>>> Measurement = MeasurementQuery()
>>> db.search(Measurement == "city temperatures")
Tag Queries
To query for tags, the tag key of interest takes the form of a query attribute (following the .
), while the tag value forms the right-hand side. An example to illustrate:
>>> from tinydb import TagQuery
>>> Tags = TagQuery()
>>> db.search(Tags.city == "Greenwich")
This will query the database for all points with the tag key of city
mapping to the tag value of Greenwich
.
Field Queries
Similar to tags, to query for fields, the field key takes the form of a query attribute, while the field value forms the right-hand side:
>>> from tinydb import FieldQuery
>>> Fields = FieldQuery()
>>> db.search(Fields.high > 50.0)
This will query the database for all points with the field key of high
exceeding the value of 50.0.
Some tag keys and field keys are not valid Python identifiers (for example, if the key contains whitespace), and can alternately be queried with string attributes:
>>> from tinydb import TagQuery
>>> Tags = TagQuery()
>>> db.search(Tags["country name"] == "United States of America")
Time Queries
To query based on time, the “right-hand side” of the TimeQuery
should be a timezone-aware datetime
object:
>>> from tinydb import TimeQuery
>>> from datetime import datetime, timezone
>>> Time = TimeQuery()
>>> db.search(Time > datetime(2000, 1, 1, tzinfo=timezone.utc))
To query for a range of timestamps, it is most-performant to combine two TimeQuery
instances with the &
operator (for more details on compound queries, see Compound Queries and Query Modifiers below):
>>> q1 = Time > datetime(1990, 1, 1, tzinfo=timezone.utc)
>>> q2 = Time < datetime(2020, 1, 1, tzinfo=timezone.utc)
>>> db.search(q1 & q2)
Note
Queries can be optimized for faster results. See Tips for TinyFlux for details on optimizing queries.
Advanced Simple Queries
Some queries require transformations or comparisons that go beyond the basic operators like ==
, <
, or >
. To this end, TinyFlux supports the following queries:
.map(…) <–> Arbitrary Transform Functions for All Query Types
The map()
method will transform the tag/field value, which will be compared against the right-hand side value from the query.
>>> # Get all points with a even value for 'number_of_pedals'.
>>> def mod2(value):
... return value % 2
>>> Field = FieldQuery()
>>> db.search(Field.number_of_pedals.map(mod2) == 0)
or:
>>> # Get all points with a measurement starting with the letter "a".
>>> def get_first_letter(value):
... return value[0]
>>> Measurement = MeasurementQuery()
>>> db.search(Measurement.map(get_first_letter) == "a")
Warning
Resist the urge to build your own time range query using the .map()
query method. This will result in slow queries. Instead, use two TimeQuery
instances combined with the &
or |
operator.
.test(…) <–> Arbitrary Test Functions for All Query Types
The test()
method will transform and test the tag/field value for truthiness, with no right-hand side value necessary.
>>> # Get all points with a even value for 'number_of_pedals'.
>>> def is_even(value):
... return value % 2 == 0
>>> Field = FieldQuery()
>>> db.search(Field.number_of_pedals.test(is_even))
or:
>>> # Get all points with a measurement starting with the letter "a".
>>> def starts_with_a(value):
... return value.startswith("a")
>>> Measurement = MeasurementQuery()
>>> db.search(Measurement.test(starts_with_a))
.exists() <–> Existence of Tag Key or Field Key
This applies to TagQuery
and FieldQuery
only.
>>> Field, Tag = TagQuery(), FieldQuery()
>>> db.search(Tag.user_name.exists())
>>> db.search(Field.age.exists())
.matches(…) and .search(…) <–> Regular Expression Queries for Measurements and Tag Values
RegEx queries that apply to MeasurementQuery
and TagQuery
only.
>>> # Get all points with a user name containing "john", case-invariant.
>>> Tag = TagQuery()
>>> db.search(Tag.user_name.matches('.*john.*', flags=re.IGNORECASE))
Compound Queries and Query Modifiers
TinyFlux also allows supports compound queries through the use of logical operators. This is particularly useful for time queries when a time range is needed.
>>> from tinydb import TimeQuery
>>> from datetime import datetime, timezone
>>> Time = TimeQuery()
>>> q1 = Time > datetime(1990, 1, 1, tzinfo=timezone.utc)
>>> q2 = Time < datetime(2020, 1, 1, tzinfo=timezone.utc)
>>> db.search(q1 & q2)
The three supported logical operators are logical-and, logical-or, and logical-not.
Logical AND (”&”)
>>> # Logical AND:
>>> Time = TimeQuery()
>>> t1 = datetime(2010, 1, 1, tzinfo=timezone.utc)
>>> t2 = datetime(2020, 1, 1, tzinfo=timezone.utc)
>>> db.search((Time >= t1) & (Time < t2)) # Get all points in 2010's.
Logical OR (“|”)
>>> # Logical OR:
>>> db.search((Time < t1) | (Time > t2)) # Get all points outside 2010's.
Logical NOT (“~”)
>>> # Negate a query:
>>> Tag = TagQuery()
>>> db.search(~(Tag.city == 'LA')) # Get all points whose city is not "LA".
Hint
When using &
or |
, make sure you wrap your queries on both sides with parentheses or Python will confuse the syntax.
Also, when using negation (~
) you’ll have to wrap the query you want to negate in parentheses.
While not aesthetically pleasing to the eye, the reason for these parenthesis is that Python’s binary operators (&
, |
, and ~
) have a higher operator precedence than comparison operators (==
, >
, etc.). For this reason, syntax like ~User.name == 'John'
is parsed by Python as (~User.name) == 'John'
which will throw an exception. See the Python docs on operator precedence for details.
Note
You cannot use and
as a substitue for &
, or
as a subsititue for |
, or not
as a substitute for ~
. The and
, or
, and not
keywords are reserved in Python and cannot be overridden, as the &
, |
, and ~
operators have been for TinyFlux queries.
The query and search operations covered above:
Simple Queries |
|
|
Match any Point with the measurement |
|
Match any Point with a timestamp prior to |
|
Matches any Point with a tag key of |
|
Matches any Point with a field key of |
Advanced Simple Queries |
|
|
Match any Point where a field called |
|
Transform and tag or field value for comparison to a right-hand side value. |
|
Matches any Point for which the function returns
|
|
Match any Point with the whole field matching the regular expression |
|
Match any Point with a substring of the field matching the regular expression |
Compound Queries and Query Modifiers |
|
|
Match Points that don’t match the query |
|
Match Points that match both queries |
|
Match Points that match at least one of the queries |