WHERE Clause 🔍
The where clause picks which time period and which rows to include before totals are calculated.
Think of it as narrowing the data going into your query — like choosing dates, lines, products, or uptime states.
Syntax
AGGREGATE <expression> AS <name>
WHERE TIME FROM <start> TO <end>
[AND <filters>]
-- or
WHERE TIME BETWEEN <start> AND <end>
[AND <filters>]
Every query that does not use from a helper query must include a time range.
Queries that use from inherit the time range from the helper query and usually do not need another time clause on the outside.
Specifying Time Range
Start and end can be:
- Fixed dates —
2024-01-01 00:00:00 - Relative to now —
now()-30D,now()-4h - Watermark —
watermark()for near-real-time data - Aligned boundaries —
date_trunc(...),adjustTimeToInterval(...)
Absolute Timestamps
Use YYYY-MM-DD HH:MM:SS:
AGGREGATE aggregation.performance AS performance
WHERE TIME FROM 2024-01-01 00:00:00 TO 2024-02-01 00:00:00
AND factory.name = 'Factory A'
GROUP BY line.name AS linename
Relative Timestamp Expressions
Format: now()-<number><unit>
Units:
- `s` → second
- `m` → minute
- `h` → hour
- `D` → day
- `W` → week
- `M` → month (28 days in bucket calculations)
- `Q` → quarter
- `Y` → year
Example (last 30 days):
AGGREGATE aggregation.performance AS performance
WHERE TIME FROM now()-30D TO now()
AND factory.name = 'Factory A'
GROUP BY line.name AS linename
Using Watermarks
watermark() returns a time based on the latest data received — useful for live dashboards.
AGGREGATE aggregation.performance AS performance
WHERE TIME FROM watermark() - 5m TO now()
AND factory.name = 'Factory A'
GROUP BY line.name AS linename
This typically means “from five minutes before the latest data arrived, through now.”
Advanced Timestamp Adjustments
date_trunc(timestamp, 'unit')— round down to the start of a hour, day, week, etc.adjustTimeToInterval(timestamp, boundary)— snap to shift or run boundaries (e.g.shift.start)
Example: Truncate to Day
AGGREGATE aggregation.performance AS performance
WHERE TIME FROM date_trunc(now()-30D, 'D') TO date_trunc(now(), 'D')
AND factory.name = 'Factory A'
GROUP BY line.name AS linename
Example: Align to Shifts
AGGREGATE aggregation.performance AS performance
WHERE TIME FROM adjustTimeToInterval(now()-30D, shift.start)
TO adjustTimeToInterval(now(), shift.start)
AND factory.name = 'Factory A'
GROUP BY line.name AS linename
AGGREGATE aggregation.performance AS performance
WHERE TIME FROM adjustTimeToInterval(now()-7D, interval.run.start)
TO adjustTimeToInterval(now(), shift.start)
AND line.name = 'Line 1'
GROUP BY line.name AS linename
Multiple time ranges
You can list more than one time from ... to ... in the same where, joined with or, when the query needs multiple windows.
Optional Filters
Beyond time ranges, WHERE supports logical filters with AND / OR.
Example: AND
AGGREGATE aggregation.performance
WHERE TIME FROM watermark() - 5m TO now()
AND line.name = 'Line 1'
AND interval.state.type = 'Uptime'
âś… All conditions must be true.
Example: OR
AGGREGATE aggregation.performance
WHERE TIME FROM watermark() - 5m TO now()
AND (
interval.state.type = 'Uptime'
OR interval.state.type = 'Scrapping'
OR product.name = 'Twizzlers'
)
âś… At least one condition must be true.
Unary NOT
You can negate a single predicate with NOT. It applies only to the predicate immediately after it (one comparison or parenthesized expression), not to the whole WHERE clause.
When it helps. Many comparisons have a built-in “opposite” you can use instead of NOT: for example != instead of negating =, NOT IN instead of IN, and IS NOT instead of IS. Unary NOT is most useful when there is no comparable inverse—common cases include:
- A
CONTAINSpredicate (OQL does not define a singleNOT CONTAINSoperator, so you negate the whole comparison withNOT). - Predicates built with functions that express a “positive” check—such as
containsAny(...)(“has any of these values/substrings”)isNotNull(...), or similar—where spelling the condition asNOT (...)is simpler or clearer than finding another comparator or function.
-- No dedicated "NOT CONTAINS"; use unary NOT on the comparison
AND NOT interval.state.comment CONTAINS 'blocked'
-- Function-style predicate; negate the whole expression
AND NOT containsAny(interval.state.comment, 'alarm', 'reset', 'fault')
NOTis case-insensitive (not,Not,NOT, etc.).- There must be at least one space or line break between
NOTand the predicate. For example,NOTline.id = '…'is invalid; useNOT line.id = '…'or putNOTon its own line before the predicate. - You cannot put a line comment (
-- …) betweenNOTand the predicate—only spaces or newlines count as separators there. NOT NOT …is not valid OQL.
The same NOT / AND / OR rules apply in HAVING when you filter on aggregated results.
NOT binds tighter than AND / OR: NOT a AND b means (NOT a) AND b, not NOT (a AND b). Use parentheses when you need to negate a group:
AGGREGATE aggregation.performance
WHERE TIME FROM watermark() - 5m TO now()
AND NOT line.id = 'uuid'
AND interval.state.type = 'Uptime'
AGGREGATE aggregation.performance
WHERE TIME FROM watermark() - 5m TO now()
AND NOT (
interval.state.type = 'Uptime'
OR product.name = 'product_name'
)
Visual Cheat Sheet
| Operator | Purpose | Example |
|---|---|---|
= |
Equal to | interval.state.type = 'Uptime' |
!= |
Not equal to | product.name != 'Widget' |
> |
Greater than | timeinif()/60 > 5 |
< |
Less than | count(interval.run) < 5 |
<= / >= |
Comparison | time.hour_of_day >= 8 |
IN / NOT IN |
Membership | line.id IN ('uuid1', 'uuid2') |
IS / IS NOT |
Null checks | interval.run.end IS NOT NULL |
CONTAINS |
Substring match | interval.state.comment CONTAINS 'blocked' |
NOT |
Negate next predicate | NOT line.id = '…' |
AND |
All conditions true | line.name = 'Line 1' AND interval.state.type = 'Uptime' |
OR |
Any condition true | product.name = 'A' OR product.name = 'B' |
✨ Use WHERE to filter raw data before aggregation. And combine with HAVING to filter aggregated results for powerful queries.