Query Language
The Neowit Query Language is a simple expression language for computing functions of the sensor data saved in the Neowit Platform. The following example averages a set of sensors and filters it to include only business hours.
Introduction
Neowit stores data collected from devices as time series of sensor data. Each sensor series has an associated sensor type which describes the type of data collected (e.g. Temperature). Because a device can collect data from multiple sensors, a device record is a set of series for each sensor type collected from the device. An expression typically computes a function of these records and returns another record. A record can contain data from multiple devices and thus contain multiple time series with the same sensor type as long as they belong to different devices.
Evaluation Context
Each expression is evaluated in an implicit device context, which consists of identifiers mapping to records and the interval and resolution in the request.
Inputs
Inputs are the identifiers in the expression that resolve to the records being computed on. For example, the input variable floor3
may resolve to a record of many devices and sensor data series.
Explicit Inputs are identifiers defined in the request associated with the query expression. They can resolve to individual devices and sensors, or they can in turn resolve to other identifiers transitively.
Implicit Inputs are identifiers that are available in every request. They are typically generated by Neowit from metadata gathered from the platform. For example, the
floor3
identifier may be generated from a space in the Neowit platform and refer to all sensor data from devices contained within that space.
Intervals
Intervals contain the time range requested (e.g. all of last week) as well as an associated resolution (e.g. hourly). When using aggregation functions, the resolution will be used as the target for which to aggregate or interpolate to. For example, sensor data is typically collected in second resolution, but when calling avg(..)
with a requested hourly resolution, the data will be average by the hour. The resulting record will always have the same resolution as requested. If no aggregation was performed in the expression, Neowit will auto-aggregate the resulting record to the target resolution.
Data Types
Each function takes a set of parameters with statically checked data types and has a return data type. A valid expression must always return a record data type, but sub-expressions may return other data types. For example, the function hours(floor3, 9, 17)
returns the data in floor3
filtered to only include data for hours between 9 and 17 (for each day). The type signature of the function is hours(rec: record, hoursIndexStart: number, hoursIndexEnd: number): record
. The data types available are loosely inspired by JSON.
Number: Any integer or floating point number.
String
Boolean
List. Lists may take on any type but they must be homogeneous, i.e. every list item must have the same type, e.g. a list of strings.
Series. An individual time series for a
(deviceId, sensorId)
pair.Record. A set of series.
Backtick Identifiers
Identifiers can be written either as a standard C-like identifier, e.g. floor3_25
or they can be written using backticks which allows them to be represented with arbitrary strings containing whitespace etc, e.g. `Floor 123`
Identifier Lists
A list of identifier, e.g. [floor2, floor3]
are automatically resolved to a record containing the data from all the identifiers in the list with an implicit combine(..)
function applied to them that aggregates records row-wise.
Function Overloading
Types are never annotated in an expression, but are inferred by the evaluated and statically checked. Each function (and binary operator) may have several overloaded type signatures allowing for inputs of different types to be used with the same function or operator.
Default Values
Some of the parameters to a function have default values making them optional. For example, in the function avg(rec: record, fillna: boolean): record
the fillna
parameter has a default value of false
so that we can simply write avg(floor3
).
Allowed Values
Some parameters may have a predefined set of allowed values (effectively enums). For example, in the function agg(rec: record, method: string, fillna: boolean): record
The parameter method
which specifies the aggregation function to use has a set of allowed values auto
, avg
,max
, etc.
Binary Operators
In addition to function, expressions can also contain binary operators. These may be either logical or arithmetic. Binary operators are overloaded similar to functions so they work with a range of data types.
Logical Operators
These typically produce records of indicator values (1s or 0s) for when the expression is true. For example, the expression floor3 < 5
returns a record of indicator values of the input record for when the values are less than 5. floor2 > floor3
produces a record of indicator values for when the sensor data of floor2
is greater than the corresponding values of floor3
.
Arithmetic Operators
Similarly, arithmetic operators allow for basic arithmetic on various data types, e.g. floor3 + floor2
will add the data from corresponding sensors in the operand records. 3 * floor3
will multiply all the values in the record.
Last updated