Skip to main content
Version: 1.0.8

Querying History

From a screen​

ComponentUse
HistorianThe full trend: live or a picked range, statistics, alarms, annotations, export
HmiTrendChartA high-performance-HMI trend beside a controlled value
HmiSparklineA small inline trend, light enough to repeat dozens of times

Bind the tags and the chart does the rest. Window selection, bucketing and aggregation are handled server-side, and every chart reads through the designated historian, which reaches earlier stores through the epoch log. 1.0.8 Up to 1.0.7 a chart read through the tag's own storage connection first. When that connection had since been replaced and deleted, the chart stayed empty while its data was being recorded.

→ Historian component

Retrieval modes Changed in 1.0.8​

A chart asks for buckets, never for more points than it can draw. What it gets per bucket depends on the mode:

ModePer bucketUsed by
M4First, minimum, maximum and last valueThe Historian chart in Live (always), and in Range when Settings → Min / Max band → Peaks kept (min / max line) is chosen; HMI trends and sparklines, with one bucket per pixel column
AverageThe meanAverage (single line)
Min / MaxThe mean, plus a shaded band from minimum to maximumMin / Max (with band)
RawEvery stored sample, as storedExport → CSV — every stored sample

M4 keeps spikes. A 200 ms current spike averaged into a one-minute bucket disappears. Drawn through each bucket's first, minimum, maximum and last values, it stays visible at every zoom level. Each stroke is drawn at the samples' own times, so a smooth signal no longer looks like stairs. If the excursion is the reason you are looking, use M4 or Min / Max, never Average.

Raw means as stored: numbers, exact integers (not rounded through floating point), booleans, strings and JSON, Bad rows included, each with its quality, status, ts_origin and microsecond timestamp. It is the only way to read a string or JSON tag's history.

How buckets are chosen​

  • Sizes snap to a ladder: 1, 2, 5, 10, 15, 30 s · 1, 2, 5, 10, 15, 30 min · 1, 2, 3, 6, 12 h · 1 day, then whole days. The size is rounded up, so a chart never gets more points than it asked for. Every rung divides a day, so the history, the live edge and the rollups share one grid.
  • Never finer than the tags are logged. When every tag on the chart has a store interval, the bucket is at least the finest of them. Finer buckets would be mostly empty, and the line would turn to dashes.
  • Hours and days follow the time zone. A day bucket starts at local midnight in the project's time zone, or the browser's when the project sets none. The offset is the zone's at the window start, so a window that crosses a daylight-saving change is an hour off after it.
  • Up to 32 tags per request, and at most 10,000 buckets.

What fills a bucket​

SituationWhat the chart shows
The bucket has Good or Uncertain rowsTheir value (Bad rows never count)
An on-change tag, holding steadyIts last value, carried across empty buckets, at most 8 hours, never across a Bad stretch → Carry
The first bucket of the window is emptyThe bounding value: the last stored value before the window, held (step) or interpolated toward the next one (linear), but only if it is recent enough to be the current reading. Older is an outage and stays a gap.
A run of empty buckets shorter than a few logging intervalsBridged with a solid line: that is the tag's cadence, not missing data
A longer runA real hole: the line breaks, with a faint dashed bridge
Not GoodShaded, with the reason under the crosshair

Which table answers​

Windows under 12 hours read the raw table. Longer windows read the coarsest rollup tier whose buckets fit exactly inside the chart's: the 1-minute, 1-hour or 1-day tier. A tier that is missing or more than 15 minutes behind is skipped, and the chart reads the next finer source. Rows replayed from a store-and-forward backlog older than a week are folded into the rollups too. 1.0.8 Up to 1.0.7 they reached the raw table but never the rollup, so every chart of 12 hours or more left them out.

Interpolation​

How stored values connect follows the tag's Interpolation. Real numbers are linear; integers follow Settings → Historian (Linear unless changed there); booleans and text step. → Interpolation

Statistics​

In Range mode, the series table shows per-tag statistics for the window. Pick the columns under Settings → Aggregations (range):

ColumnNotes
Average, Minimum, Maximum, Count, SumOver the stored rows, Bad rows left out
First, LastBy timestamp
Std Deviation (sample / population), Variance, RangeRebuilt exactly from (Σx², Σx, n) across epochs
Time-weighted average 1.0.8Each value weighted by how long it held, from the value before the window to the end. Step or linear per the tag's interpolation. Bad time is left out, not counted as zero.
% Good 1.0.8Share of the window the tag was Good. Time with no known value is not Good.
Total (integral) 1.0.8A rate totalled over time: a flow in m³/h totals to m³. Set the unit with Settings → Total per (per second, minute, hour or day; default hour). Bad time and gaps add nothing.
Counter increase 1.0.8How much an ever-rising counter went up. Readings are chained across Bad gaps. A fall to half the previous value or less is a reset or wrap, and the new value counts. A smaller fall is a glitch and adds nothing, so the increase is never negative.
Time on, Time off 1.0.8Time the value was non-zero or zero, Bad time left out
Starts 1.0.8How many times a stored zero was followed by a stored non-zero

The newer statistics are worked out inside QuestDB, not by pulling every row out of it. Tags with no value in the window show no Total, Time on or Starts, rather than a misleading 0.

Long ranges are approximate. Time-weighted average, % Good and Total over a range longer than 7 days are computed from the 1-hour tier: each hour counts its own average. The table marks such a value with ~, and its tooltip gives the resolution. That is exact for evenly sampled tags; a tag stored on change can be off by up to one hour per change. In measurements on a real store it was within 0.15 % for most tags. Counter increase, time on and off, and starts always read the raw rows.

The Quality column summarises each tag's window, for example Good, or Bad 12m · Uncertain 3m. The stats CSV adds Quality, Bad (s) and Uncertain (s).

The value at an instant New in 1.0.8​

The tank level at shift change: system.hist.valueAt reads it from the rows on either side of the instant, by the tag's interpolation. Step holds the value at or before the instant. Linear follows the line to the next valued, non-Bad row. A row exactly at the instant is itself. A value too old to say what the tag held then is reported as unknown, not as a stale number. A Bad row before the instant gives Bad with no value. It reads the store that was the historian at that instant.

system.hist.getStats returns the statistics above for a script. → System API

From a script​

Python
data = system.hist.getTrends(
["Line1/Filler/Motor1/Speed", "Line1/Filler/Motor1/Current"],
"2026-08-01T00:00:00Z",
"2026-08-02T00:00:00Z",
maxPoints=500,
agg="minmax",
)

agg is avg (the default) or minmax. Always pass maxPoints. A day of one-second data is 86,400 points per tag, and a chart 1,200 pixels wide can draw about 1,200. → System API

Live versus range mode​

ModeBehaviour
LiveA rolling window that follows now. Always M4. The live tail buffer merges the newest in-memory samples, so the leading edge is genuinely live despite QuestDB's batched flush. A Bad reading breaks the live edge the same way it breaks the history.
RangeA fixed window, with statistics, zoom and pan, and the Min / Max band choice.

Exporting history​

The Historian chart's Export menu offers:

ItemContains
PNG imageThe chart as drawn
CSV — stats tableOne row per visible tag, one column per statistic
CSV — time seriesThe points the chart draws, one per bucket
CSV — every stored sample 1.0.8Every stored row of the visible window, at full resolution

CSV — every stored sample is built by the server and streamed to the browser, so a large export never sits in the page's memory. It has one row per stored sample (long format): Timestamp (ISO 8601 in the project time zone, with its offset and microseconds), Tag (namespace path), Value, Quality, Status and Origin. Each tag keeps its own timestamps; a wide table would have to invent values where one tag has a sample and another does not. A value starting =, +, - or @ that is not a number is neutralised against spreadsheet formula injection. The file stops at 1,000,000 rows with a closing # truncated line. A failure part-way through is stated at the end of the file. A live chart exports up to now.

1.0.8 The time-series CSV writes timestamps in the project time zone with their offset (2026-10-09T13:00:00.123+07:00), matching the axis. Up to 1.0.7 it wrote UTC.

Other routes out of QUBIQ:

  • A Gateway script that calls system.hist.getTrends or getStats, formats the result and writes it with system.files.save.
  • A pipeline on a schedule doing the same and pushing to an external system.
  • Direct SQL against QuestDB through a named query, for tools that speak Postgres wire.

→ Gateway scripts

What keeps reads fast New in 1.0.8​

  • Abandoned reads are cancelled in QuestDB. Zoom away, close the screen or time out, and the query stops in QuestDB too. It no longer runs to completion holding one of the historian's query slots.
  • Reads fail fast while a store is unreachable. After a failed connect, every read for that store fails at once with store unreachable, saying when it will retry: after 2 s, doubling to at most 30 s. Before, each chart, statistics or annotation read waited out its own 10-second connect timeout. A page with a few charts could hold every browser connection and freeze the interface for half a minute. A slow query on a live store does not trip this.
  • One request per window for HMI trends. HMI trends and sparklines mounted together on one window are merged into a single historian request, up to 32 tags each.
  • An oversized reply fails at once with too large… use fewer tags, a coarser resolution or a shorter time range. Up to 1.0.7 it was never sent, and the chart waited out its whole timeout before showing a generic failure.
SymptomCauseFix
Slow long-range queryA rollup tier is behind or invalid, so the chart reads rawLook for Historian rollup needs a rebuild
too large errorMany tags at high resolutionFewer tags, a coarser resolution, or a shorter range
store unreachableQuestDB is down or unreachableFix the store; reads resume on their own
Trend has gapsA real outage, a Bad stretch, or a deadband larger than the movementHover the shading for the reason
Trend flat-lines then jumpsAn on-change tag held, then movedExpected; set a store interval to prove it was alive
Trend stops at a date in the pastAn epoch boundary with a retired storeCheck History timeline in Settings → Historian
Statistics marked ~A range over 7 days, read from the 1-hour tierExpected; narrow the range for an exact value

Interpreting a trend honestly​

  • A deadband means the stored trend is not the raw signal. That is intended, but know the band before drawing conclusions about noise.
  • Average smooths. Switch to M4 or Min / Max before concluding an excursion did not happen.
  • A shaded stretch is not zero. It is time the tag was Bad or Uncertain. Hover it for the reason.
  • A carried line is a held value, not a measurement. It stops at Bad, at 8 hours, and at outage markers. Within those limits it is what the tag held.

Next​

→ Annotations · Security