EQL — Event Query Language Reference
Table of Contents
- Overview
- Where EQL Is Used
- Basic Syntax
- Event Streams (14 + 3 Internal)
- Windows
- WHERE Clauses
- Aggregate Functions
- Grouping and Ordering
- JOIN — Combining Streams
- Pattern Matching
- Time and State Functions
- Variables and Contexts
- 12 Practical Examples
- Performance Guide
- FAQ
- Related Screens
Overview
Primary location Run queries directly from the left menu Automation > EQL Query (
/query/index). The same syntax is also used in the condition expressions of advanced EQL alarms, triggers and statements, and flows.
EQL (Event Query Language) is PlantPulse Platform's own query language, built so you can query, aggregate, and pattern-match real-time streaming data using SQL-like syntax. Where ordinary SQL operates on static tables, EQL operates on continuously arriving event streams and emits results moment by moment.
With a single line of EQL you can implement:
- Real-time monitoring — emit a result the instant a tag value exceeds a threshold
- Asset-level aggregation — automatically compute average/max/min every 5 minutes
- Pattern matching — "raise an alarm if event B does not follow event A within 30 seconds"
- Time series joins — time-aligned comparison of two tags
EQL uses nearly the same syntax as SQL, with the added concept of a window that specifies a time or length range such as "the last N seconds / N events".
Where EQL Is Used
EQL is used in the following places within the Platform.
EQL Query, Trigger, and Statement sit side by side under the left menu Automation section.
(There is no menu named CEP — older documents listed these three as sub-screens of CEP.)
| Menu · Screen | Internal URL | Purpose |
|---|---|---|
| Automation > EQL Query — docs | /query/index | Operators run EQL ad hoc and inspect the results (exploration, debugging) |
| Automation > Trigger — docs | /trigger/index | Automatically publish EQL match results to an MQ topic or to storage |
| Automation > Statement — docs | /statement/index | Rules registered per asset (state / aggregation / event / command) |
| Alarm → EQL Alarm | — | Raise alarms on arbitrary conditions using EQL rules |
| Flow → Script | — | EQL expression mode of flow_script_filter / flow_script_transform / flow_switch |
| Tag form — AI tab | — | Input conditions for prediction and anomaly detection rules |
EQL is read-only — it never modifies data. To raise an alarm or invoke a flow action node from a match, route the result through CEP → Trigger or an EQL alarm.
Basic Syntax
The basic structure of EQL is almost identical to SQL.
SELECT <column-or-function>
FROM <stream>.<window>
WHERE <condition>
GROUP BY <field>
HAVING <aggregation-condition>
ORDER BY <field> [ASC|DESC]
The simplest EQL:
SELECT * FROM Point.win:time(1 sec)
Emits every tag point received in the last 1 second, once per second.
Wildcards and Column Selection
| Syntax | Meaning |
|---|---|
SELECT * | Include all columns |
SELECT tag_id, value | Specific columns only |
SELECT value AS v, tag_id AS id | Assign an alias |
SELECT value * 9 / 5 + 32 AS fahrenheit | Result of an arithmetic expression |
SELECT CASE WHEN value > 80 THEN 'HIGH' ELSE 'OK' END AS level | Conditional expression |
Comments
-- 한 줄 주석
/* 여러 줄
주석 */
SELECT * FROM Point.win:time(1 sec)
Event Streams (14 + 3 Internal)
The streams (event sources) that may appear in the FROM clause of an EQL statement are listed below. All streams are generated automatically by the Platform and can be used immediately without any registration.
Using a name not listed here in
FROMmeans the rule will not deploy. Only registered event types can be streams (plantpulse.core.engine.eql.EventTypes).
Tag Domain (3)
| Stream | When It Fires | Key Fields |
|---|---|---|
| Point | Every time a tag value is received | tag_id, value, quality, ts, site_id, asset_id, unit, java_type |
| PointMap | Same, but used with the tag('<id>') function to look up multiple tags as key-value pairs | (PointMap-specific helpers) |
| Alarm | When a tag alarm occurs | tag_id, alarm_band, value, threshold, priority, band_message |
Asset Domain (6)
| Stream | When It Fires | Key Fields |
|---|---|---|
| AssetData | Asset-level time series data (aggregation results) | asset_id, values (key-value), ts |
| AssetEvent | Asset event occurs (start, stop, maintenance, etc.) | asset_id, event_type, details, ts |
| AssetAlarm | Asset alarm occurs | asset_id, alarm_band, priority, message |
| AssetCommand | When a command is issued to an asset | asset_id, command, args |
| AssetAggregation | 1-minute / 5-minute / 1-hour asset aggregation results | asset_id, window, agg_type, values |
| AssetContext | Asset context (metadata) change | asset_id, context_key, before, after |
Older documents listed these two as asset domain streams, but they are not registered event types.
Writing FROM AssetHealthStatus... will prevent the rule from deploying.
Health and connection status evaluation runs as a scheduled job, not as a stream
(AssetHealthStatusDeployer / AssetConnectionStatusDeployer). View those results on screen or via the API. If you want to catch
state changes in EQL, detect the absence of the source AssetEvent · AssetAlarm or
Point using the not pattern.
Production Domain (3)
| Stream | When It Fires | Key Fields |
|---|---|---|
| Calendar | Shift start / end / change | shift_id, calendar_id, event, start_time, end_time |
| WorkOrder | Work order lifecycle transition | order_id, status, prev_status, asset_id, product_id |
| OEE | OEE evaluation result updated | order_id, asset_id, oee, availability, performance, quality |
System Domain (2)
| Stream | When It Fires | Key Fields |
|---|---|---|
| Log | System diagnostic message | module, level, code, summary, ts |
| OSPerformance | Server OS resource metrics (periodic) | CPU, memory, disk, etc. |
No event type named Status is registered. If you intended to catch OPC or Edge connection
state changes with EQL, that stream will not work — instead detect the absence of Point
using the not pattern.
Internal Streams (3)
These are registered but are not intended for authoring rules. They are used for diagnostics and health checks.
| Stream | Purpose |
|---|---|
| DateTime | Periodically fired time event (trigger for time-based rules) |
| Ping | Engine liveness check |
| Test | Rule validation |
Every stream must be combined with a window before use (as in
Point.win:time(1 sec)). If you useFROM Pointalone without a window, the result disappears immediately and is never visible.
Windows
A window is "a slice of time or length taken from a stream". EQL supports two kinds of windows.
Time Window
| Syntax | Meaning |
|---|---|
Point.win:time(1 sec) | All events received in the last 1 second (sliding) |
Point.win:time(10 sec) | Last 10 seconds |
Point.win:time(5 min) | Last 5 minutes |
Point.win:time(1 hour) | Last 1 hour |
Supported time units: sec (seconds) · min (minutes) · hour (hours) · day (days).
Length Window
| Syntax | Meaning |
|---|---|
Point.win:length(100) | The last 100 events |
Point.win:length(1000) | The last 1,000 events |
Batch Window
win:time_batch / win:length_batch — emits once only at the moment the window fills up or the time expires. Saves resources.
-- 매 1분마다 한 번씩 그 1분간 평균을 emit
SELECT avg(value) FROM Point.win:time_batch(1 min) WHERE tag_id = 'MOTOR-001.TEMP'
Window Selection Guide
| Situation | Recommended Window |
|---|---|
| Real-time monitoring (viewing raw values) | win:time(1 sec) |
| Short-term average/max (e.g. 5-minute average) | win:time(5 min) |
| Hourly aggregation | win:time_batch(1 hour) |
| Comparing the last N events | win:length(N) |
| Tracking rate of change | win:length(2) + the prev() function |
WHERE Clauses
The same comparison operators as SQL are used.
| Operator | Example | Meaning |
|---|---|---|
= != <> | tag_id = 'MOTOR-001' | Equal / not equal |
< <= > >= | value > 80 | Comparison |
BETWEEN | value BETWEEN 70 AND 90 | Range |
IN | tag_id IN ('A','B','C') | List membership |
LIKE | tag_id LIKE 'MOTOR-%' | Pattern matching (% wildcard) |
IS NULL / IS NOT NULL | quality IS NOT NULL | Null check |
AND OR NOT | value > 80 AND quality = 'GOOD' | Logical combination |
Regular Expressions
SELECT * FROM Point.win:time(1 sec)
WHERE tag_id REGEXP '.*\\.TEMP$'
Matches only tags ending in
.TEMP. Backslashes must be doubled (\\.).
Dynamic Site / Asset Filter
SELECT * FROM Point.win:time(1 sec)
WHERE site_id = 'SITE-A'
AND asset_id LIKE 'LINE-1.%'
AND value > 75
Aggregate Functions
These aggregate the events within a window.
Basic Aggregates
| Function | Meaning | Example |
|---|---|---|
count(*) | Event count | SELECT count(*) FROM Point.win:time(10 sec) |
sum(field) | Sum | sum(value) |
avg(field) | Average | avg(value) |
min(field) | Minimum | min(value) |
max(field) | Maximum | max(value) |
median(field) | Median | median(value) |
stddev(field) | Standard deviation | stddev(value) |
variance(field) | Variance | variance(value) |
first(field) | First value in the window | first(value) |
last(field) | Last value in the window | last(value) |
DISTINCT Aggregation
SELECT count(distinct tag_id) AS unique_tags
FROM Point.win:time(1 min)
Combined with CASE
SELECT
count(CASE WHEN value > 80 THEN 1 END) AS high_count,
count(CASE WHEN value <= 80 THEN 1 END) AS ok_count
FROM Point.win:time(1 min)
WHERE tag_id = 'MOTOR-001.TEMP'
Time Series Functions
| Function | Meaning | Example |
|---|---|---|
prev(N, field) | Value N steps back | value - prev(1, value) (difference from the previous value) |
prevwindow(field) | Array of all previous values in the window | prevwindow(value) |
rate(field) | Rate of change per unit time | rate(value) |
Grouping and Ordering
GROUP BY
-- 자산별 최근 1분 평균 온도
SELECT asset_id, avg(value) AS avg_temp
FROM Point.win:time(1 min)
WHERE tag_id LIKE '%.TEMP'
GROUP BY asset_id
HAVING — Filtering After Aggregation
-- 평균 > 80 인 자산만
SELECT asset_id, avg(value) AS avg_temp
FROM Point.win:time(1 min)
WHERE tag_id LIKE '%.TEMP'
GROUP BY asset_id
HAVING avg(value) > 80
ORDER BY · LIMIT
-- 가장 뜨거운 5개 자산
SELECT asset_id, max(value) AS peak
FROM Point.win:time(5 min)
WHERE tag_id LIKE '%.TEMP'
GROUP BY asset_id
ORDER BY peak DESC
LIMIT 5
LIMITincludes only the top N rows at the moment the result is emitted.
JOIN — Combining Streams
Two or more streams can be time-aligned and joined.
-- 같은 자산에서 같은 시간대의 온도와 압력을 한 행으로
SELECT p1.value AS temp, p2.value AS pressure, p1.asset_id
FROM Point.win:time(1 sec) AS p1,
Point.win:time(1 sec) AS p2
WHERE p1.tag_id LIKE '%.TEMP'
AND p2.tag_id LIKE '%.PRESSURE'
AND p1.asset_id = p2.asset_id
Joining Asset Events with Work Orders
SELECT e.asset_id, e.event_type, w.order_id, w.status
FROM AssetEvent.win:time(5 min) AS e,
WorkOrder.win:time(5 min) AS w
WHERE e.asset_id = w.asset_id
AND e.event_type = 'STARTUP'
AND w.status = 'START'
Join results are produced only while the time windows of the two streams overlap. When comparing data with different time granularities, align them to a similar cycle with
win:time_batch.
Pattern Matching
EQL's most powerful feature — you can define sequences such as "B does (not) occur within N seconds after event A".
every — Match Every Time
SELECT * FROM pattern [
every a = AssetEvent(event_type = 'STARTUP')
-> b = AssetEvent(event_type = 'SHUTDOWN', asset_id = a.asset_id)
where timer:within(60 sec)
]
Matches every pair of start event → stop event within 60 seconds. Detects the abnormal pattern of stopping immediately after a brief run.
not — Does Not Occur for N Time
SELECT * FROM pattern [
every a = AssetEvent(event_type = 'STARTUP')
-> ( timer:interval(30 min)
and not AssetData(asset_id = a.asset_id) )
]
Matches when no data arrives for 30 minutes after the start (detects unresponsive assets).
and / or — Both, or Either One
SELECT * FROM pattern [
( every AssetAlarm(priority = 'ERROR') )
and
( every WorkOrder(status = 'START') )
]
Key Pattern Matching Keywords
| Keyword | Meaning |
|---|---|
every <e> | Matches every time e occurs |
e1 -> e2 | e2 occurs after e1 (ordered match) |
e1 and e2 | Both events occur (order irrelevant) |
e1 or e2 | Either one occurs |
not e | e does not occur |
timer:within(<duration>) | Matches only within the specified time |
timer:interval(<duration>) | Waits for the specified time |
Time and State Functions
Time Functions
| Function | Meaning |
|---|---|
current_timestamp | Current time (ms) |
current_date() | Today's date (yyyy-MM-dd) |
timestamp(field) | Extracts the event timestamp |
hour_of_day(ts) | 0–23 hours |
day_of_week(ts) | 1 (Sun) – 7 (Sat) |
minute_of_hour(ts) | 0–59 |
dayofmonth(ts) | 1–31 |
-- 야간 (22시~6시) 발생 알람만
SELECT * FROM Alarm.win:time(1 hour)
WHERE hour_of_day(ts) >= 22 OR hour_of_day(ts) < 6
State Functions
| Function | Meaning |
|---|---|
tag('<tag_id>') | The most recent value of that tag (PointMap context) |
prev_status(asset_id) | The asset's previous state |
health(asset_id) | Asset health status (OK/WARN/ERROR/UNKNOWN) |
is_in_shift(asset_id) | Whether the asset is within a shift |
Arithmetic and String Functions
| Function | Meaning |
|---|---|
abs(x) / round(x, n) / floor(x) / ceil(x) | Math |
pow(x, y) | x raised to the power of y |
sqrt(x) | Square root |
length(s) | String length |
upper(s) / lower(s) | Case conversion |
substring(s, start, len) | Substring |
concat(a, b, ...) | Concatenation |
Variables and Contexts
Variables — Dynamic Thresholds
-- 변수 정의 (운영자가 화면에서 조정 가능)
create variable double max_temp = 80;
-- 변수 사용
SELECT * FROM Point.win:time(1 sec)
WHERE tag_id LIKE '%.TEMP' AND value > max_temp
Variable values can be changed during operation, and every EQL rule uses the new value immediately.
Contexts — Time- or Condition-Based Activation
The Platform ships with commonly used contexts predefined, so you can use them as they are.
| Context | Activation Condition |
|---|---|
EVERY_1_MINUTES | Every 1 minute |
EVERY_5_MINUTES | Every 5 minutes |
EVERY_10_MINUTES | Every 10 minutes |
EVERY_30_MINUTES | Every 30 minutes |
EVERY_1_HOURS | Every 1 hour |
EVERY_3_HOURS | Every 3 hours |
EVERY_6_HOURS | Every 6 hours |
EVERY_12_HOURS | Every 12 hours |
DAY_WORK_TIME | Weekdays 09:00–18:00 |
-- 5분마다 한 번씩 평균 온도 emit (그 사이에는 결과가 안 옴)
context EVERY_5_MINUTES
SELECT avg(value) AS avg_temp
FROM Point.win:time(5 min)
WHERE tag_id = 'MOTOR-001.TEMP'
-- 주간 근무시간에만 평가
context DAY_WORK_TIME
SELECT * FROM AssetAlarm.win:time(1 sec)
WHERE priority = 'ERROR'
12 Practical Examples
Example 1 — Real-Time Monitoring of a Single Tag
SELECT value, ts, quality
FROM Point.win:time(1 sec)
WHERE tag_id = 'MOTOR-001.SPEED'
Example 2 — Immediate Threshold Breach Detection
SELECT tag_id, value, ts
FROM Point.win:time(1 sec)
WHERE tag_id LIKE '%.TEMP' AND value > 90
Example 3 — 5-Minute Average per Asset (Emitted Every 5 Minutes)
context EVERY_5_MINUTES
SELECT asset_id, avg(value) AS avg_temp, max(value) AS peak
FROM Point.win:time(5 min)
WHERE tag_id LIKE '%.TEMP'
GROUP BY asset_id
Example 4 — Change Amount (Versus the Previous Value)
SELECT tag_id, value, value - prev(1, value) AS delta
FROM Point.win:length(2)
WHERE tag_id = 'TANK-001.LEVEL'
Example 5 — ERROR Alarms During the Night Shift Only
SELECT tag_id, priority, band_message, ts
FROM Alarm.win:time(1 sec)
WHERE priority = 'ERROR'
AND hour_of_day(ts) >= 22 OR hour_of_day(ts) < 6
Example 6 — Asset Health Transition (OK → WARN)
SELECT asset_id, status, prev_status, ts
FROM AssetHealthStatus.win:time(1 sec)
WHERE prev_status = 'OK' AND status = 'WARN'
Example 7 — Poor Work Order OEE
SELECT order_id, asset_id, oee, availability, performance, quality
FROM OEE.win:time(1 sec)
WHERE oee < 0.6
Example 8 — Detecting Assets Unresponsive for 30 Minutes
SELECT * FROM pattern [
every a = AssetData()
-> ( timer:interval(30 min)
and not AssetData(asset_id = a.asset_id) )
]
Example 9 — Stopping Immediately After Start (Unstable Operation)
SELECT * FROM pattern [
every a = AssetEvent(event_type = 'STARTUP')
-> b = AssetEvent(event_type = 'SHUTDOWN', asset_id = a.asset_id)
where timer:within(60 sec)
]
Example 10 — Simultaneous Comparison of Multiple Tags (PointMap)
SELECT
tag('MOTOR-001.TEMP') AS temp,
tag('MOTOR-001.PRESSURE') AS pressure,
tag('MOTOR-001.VIBRATION') AS vibration
FROM PointMap.win:time(1 sec)
Example 11 — Hourly Cumulative Production per Line
context EVERY_1_HOURS
SELECT asset_id, sum(value) AS hourly_count
FROM AssetAggregation.win:time(1 hour)
WHERE agg_type = 'PRODUCED' AND asset_id LIKE 'LINE-%'
GROUP BY asset_id
Example 12 — Top 10 Assets by Alarm Count
context EVERY_10_MINUTES
SELECT asset_id, count(*) AS alarm_count
FROM AssetAlarm.win:time(10 min)
GROUP BY asset_id
ORDER BY alarm_count DESC
LIMIT 10
Performance Guide
1. Keep Window Sizes Deliberately Small
-- ❌ 나쁜 예 — 24시간을 메모리에 유지
SELECT avg(value) FROM Point.win:time(24 hour)
-- ✅ 좋은 예 — 1시간만 메모리에 유지 + context 로 emit 주기 분리
context EVERY_1_HOURS
SELECT avg(value) FROM Point.win:time(1 hour)
2. Put WHERE Clauses Close to the Trigger
Place static filters such as tag ID, asset ID, and site ID at the very front of the WHERE clause. They are evaluated before any subsequent GROUP BY / HAVING.
3. LIKE '%...%' Double-Sided Wildcards Are Expensive
-- ❌ 나쁜 예
WHERE tag_id LIKE '%TEMP%'
-- ✅ 좋은 예
WHERE tag_id LIKE '%.TEMP' -- 접미사 매칭만
4. Pair Pattern Matching with Short Windows
Do not omit the time qualifier in pattern [ ... timer:within(<n-minutes>) ]. An unbounded pattern occupies memory indefinitely.
5. Tune the Result Emit Frequency
Using the context EVERY_N_MINUTES context, you can have EQL evaluate every second while emitting only every N minutes — separating the two reduces downstream load.
FAQ
Q. I entered an EQL query but nothing appears in the results panel.
A. Check the following. ① Did you use FROM Point alone — a window is required (Point.win:time(1 sec)). ② Is the WHERE condition so narrow that nothing matches? ③ Did you press the ▶ Start button at the top right? ④ Is the results panel in ⏸ paused state?
Q. How does this differ from SQL?
A. The biggest difference is the window concept. Because EQL operates on an unbounded stream, you must specify "which range". Beyond that, pattern [ ... ] sequence matching and context time contexts are features SQL does not have.
Q. What is the difference between win:time(1 sec) and win:time_batch(1 sec)?
A. win:time(1 sec) is sliding — the result is updated as each event arrives. win:time_batch(1 sec) is tumbling — events are grouped in 1-second buckets and emitted once at the moment the second expires. Use batch if you need a result exactly once per second.
Q. What is the difference between pattern [ ... ] and a plain SELECT ... WHERE ...?
A. SELECT evaluates a single event (or window), whereas pattern evaluates an event sequence (B after A, B without A, and so on). Use pattern when time order or causality matters.
Q. Do registered variables take effect immediately?
A. Yes. When you change the value of a variable defined with create variable on screen, all already-deployed EQL rules use the new value from the next evaluation.
Q. How do I send EQL match results as alarms? A. Register the EQL under CEP → Trigger and enable the "MQ streaming" or "Save to storage" option, or register the EQL under Alarm → EQL Alarm so that an alarm is published automatically on a match.
Q. Can I apply an asset-level rule to all assets at once? A. Define an asset-level EQL rule in CEP → Statement and map that rule to an asset category in the model/ontology; it will then be deployed automatically to every asset in that category. It is applied automatically whenever a new asset is added.
Q. Can a single EQL statement handle two streams at once?
A. Yes. As in the examples in the JOIN section above, list both streams comma-separated in the FROM clause and write the join condition in WHERE.
Q. I am getting far too many results.
A. Take only the top N with LIMIT N, lengthen the emit interval with context EVERY_N_MINUTES, or group results into a batch window using win:time_batch. Tightening the WHERE clause is also effective.
Q. Where can I practice writing EQL? A. The CEP → Query screen is the EQL practice tool. Enter a line, press ▶ to run, and you get results immediately. Start by copying from the 12 Practical Examples above.
Related Screens
- CEP — screens for authoring EQL queries, triggers, and statements
- Alarm → EQL Alarm — raise alarms on arbitrary conditions with EQL
- Flow — EQL mode of
flow_script_filter/flow_script_transform/flow_switch - Tag form — AI tab — EQL conditions for prediction and anomaly detection rules
- Domain ID unified search — quick lookup of tag/asset IDs for use in EQL (Ctrl+K)