Query dialects
The five clauses — filter, select, sort, group, limit — look the same everywhere, but three
kinds of store answer them slightly differently. This page is the diff, so you do not have to read three
long pages to find it.
The three dialects at a glance
| Generic search | Time series | Data sets | |
|---|---|---|---|
| URL | /north/v80/search/<resource> |
/north/v80/timeseries/provision/organizations/{org}/{id}/data |
/north/v80/datasets/provision/organizations/{org}/{id}/data |
| Field names | Dotted paths of the resource | bucketColumn, identifierColumn, columns.name, context.name |
identifierColumn, columns.name |
filter |
Full operator set | Same syntax, only on filterable columns | Same syntax, only on filterable columns |
select |
Objects with name and fields |
Same keys as filter |
Array of column names |
sort |
An object of parameters, each a field and a direction |
A string: the identifier of a sort declared in the definition | A string: the identifier of a sort declared in the definition |
group |
Supported | Not applicable | Does not exist |
limit |
start and size |
Same, with CSV caveat below | Same, with CSV caveat below |
| Response | Array named after the resource | columns plus data matrix |
columns plus data matrix |
| CSV output | — | Yes | Yes |
Why time series and data sets are stricter
Both are pre-computed projections: you declare their columns up front, and the platform builds indexes for exactly those. That is what makes them fast, and it is also why you cannot filter or sort on an arbitrary field.
Sorting is declared, not composed. A generic search accepts any field in its sort object. A time series
or a data set accepts only the identifier of a sort declared in its definition — a named, ordered list of
columns with directions — plus the reverse of each one, which the platform exposes automatically because the
same index serves it backwards. There is no per-column sortable flag and no cap on how many sorts a
definition may hold.
The real limit is a budget, not a number. Each filterable column and each declared sort consumes
optimization units, and each definition has a budget of them. Both stores offer an optimizationPlan
endpoint that reports what a definition would consume before you commit, and expose
usedSearchOptimizationUnits and freeSearchOptimizationUnits on the definition itself.
See Defining a time series and Defining a data set.
Filters have four modes, not two: NO, YES for optional equality, ALWAYS for a filter every query
must supply, and RANGE for >, < and BETWEEN. RANGE applies to numeric columns only; date-time
columns are always range-searchable.
The matrix response
Generic searches return objects, one per row. Time series and data sets return a matrix instead: a
columns array naming the fields, and a data array of rows, each row an array of values in that same
order.
Read the values off columns rather than hardcoding positions. If you do rely on the order, this is what it
is when you omit select:
| Store | Column order without select |
|---|---|
| Time series | bucketColumn, then identifierColumn, then the context columns, then the aggregated columns |
| Data sets | identifierColumn, then the defined columns.name in declaration order |
Time series additionally offer an aggregated read, POST .../{id}/dataset, which collapses every bucket
of a device into a single output row. There select.columns takes a column, an alias and an
aggregation function per output variable, and the result is always sorted ascending by identifierColumn,
which is included whether you ask for it or not. See Time series.
CSV output changes the rules
Time series and data sets can answer in CSV instead of JSON, and that switch changes two behaviours that surprise people:
limit flips meaning. In JSON, omitting limit applies the configured defaults. In CSV, omitting it
means give me everything:
| Body | JSON response | CSV response |
|---|---|---|
{"filter": {}, "limit": {"size": 500, "start": 1}} |
500 rows from row 1 | 500 rows from row 1 |
{"filter": {}} |
Configured default page | The complete data set |
{"filter": {}, "limit": {}} |
Configured default page | Error: an incomplete limit is rejected |
Sorting is disabled. CSV retrieval turns sorting off deliberately, to keep large exports fast. If you need ordered output, either sort downstream or use the JSON response.
Complete retrieval is expensive
Omitting limit in CSV mode downloads the entire store. On a large time series that is a long, heavy request.
Page it unless you genuinely want everything.
CSV formatting — the quoting character, the escape character, the end-of-line sequence and how nulls are represented — is customizable through HTTP header options, and you are responsible for the result being well-formed CSV.
Undocumented header names
The specific header names for those CSV options are not currently published in the API specification. Until they are, ask your platform contact for the exact names.
What stays the same
Worth stating plainly, because it is most of the surface:
POSTwith a JSON body, always.X-ApiKeyfor authentication.- The filter operators —
eq,neq,like,gt,lt,gte,lte,in,nin,exists,and,or— behave identically in all three dialects. limitusesstartandsizeeverywhere.- The
utc=trueheader option returns date fields in UTC in all of them.