Skip to main content
Version: Next

QuestDB

Writes time series at volume, or queries them back. Use it for series you generate yourself — a calculated OEE, a derived rate — that deserve a trend but are not tags.

Type: questdb · Category: Datasource · Ports: one input · one output

The QuestDB node as it appears on the pipeline canvas.The QuestDB node as it appears on the pipeline canvas.

When to use it

  • Storing a calculated value you want to chart over time
  • High-rate writes that would overwhelm a relational database
  • Querying history back for a report
Reach for something else when

For tag history, enable history on the tag instead — the historian already does this, correctly, with rollups and retention.

Settings

SettingKeyDefaultPurpose
Modemodeingressingress (high-rate writes) or query.
ConnectionconnectionIdWhich QuestDB connection.
TabletableIngestion target.
SymbolssymbolsWhich fields are indexed labels rather than values.
QueryqueryFor query mode.
Timeout / retriestimeout, maxRetries, retryDelay0Per-write limits.

Example — writing a derived series

OEE is calculated, not measured, so it is not a tag — but you still want to trend it.

Mode: ingress
Table: oee_by_line
Symbols: line, shift

In

{
"payload": {
"line": "LINE1",
"shift": "A",
"availability": 0.92,
"performance": 0.95,
"quality": 0.97,
"oee": 0.847,
"timestamp": 1786454700000
}
}

What you can reference

PathResolves to
{{payload}}The whole payload
{{payload.units}}A field, at any depth — {{payload.order.line}} works
{{topic}}The message topic
{{timestamp}} · {{id}}The message timestamp and id
{{vars.shift}}Message metadata, also available as {{metadata.shift}}
{{origin.id}} · {{origin.type}}The node that produced the message

With no upstream message the query runs exactly as typed, with nothing substituted — which is what makes a plain SELECT work behind an Inject.

A path that does not exist is left as literal text

{{payload.data}} against a message with no data field is not blanked and not bound — the token stays in the statement exactly as you typed it and is handed to the database as raw SQL:

-- you wrote
WHERE units = {{payload.units}}
WHERE data = {{payload.data}}

-- the database receives
WHERE units = ? -- bound, value 42
WHERE data = {{payload.data}} -- raw text, no argument

Which is a syntax error, and the error the database returns names the {{, not the missing field. The rule is deliberate — a silent NULL would make a WHERE match the wrong rows and a DELETE match too many — but it means a typo in a path fails at the database, not in the editor. Check the path against a real message in the Debug node before you rely on it.

Note this is the opposite of what the notification and AI nodes do with the same syntax, where an unresolved token becomes empty.

Gotchas

  • Mark labels as symbols, never values. A symbol is stored as a repeated label and is what you filter and group by. Making a continuously varying number a symbol builds an enormous index for nothing, and it is painful to undo once the table has data in it.

See also