Skip to main content

EQL — Event Query Language Reference

Table of Contents


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 · ScreenInternal URLPurpose
Automation > EQL Querydocs/query/indexOperators run EQL ad hoc and inspect the results (exploration, debugging)
Automation > Triggerdocs/trigger/indexAutomatically publish EQL match results to an MQ topic or to storage
Automation > Statementdocs/statement/indexRules registered per asset (state / aggregation / event / command)
Alarm → EQL AlarmRaise alarms on arbitrary conditions using EQL rules
Flow → ScriptEQL expression mode of flow_script_filter / flow_script_transform / flow_switch
Tag form — AI tabInput 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

SyntaxMeaning
SELECT *Include all columns
SELECT tag_id, valueSpecific columns only
SELECT value AS v, tag_id AS idAssign an alias
SELECT value * 9 / 5 + 32 AS fahrenheitResult of an arithmetic expression
SELECT CASE WHEN value > 80 THEN 'HIGH' ELSE 'OK' END AS levelConditional 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 FROM means the rule will not deploy. Only registered event types can be streams (plantpulse.core.engine.eql.EventTypes).

Tag Domain (3)

StreamWhen It FiresKey Fields
PointEvery time a tag value is receivedtag_id, value, quality, ts, site_id, asset_id, unit, java_type
PointMapSame, but used with the tag('<id>') function to look up multiple tags as key-value pairs(PointMap-specific helpers)
AlarmWhen a tag alarm occurstag_id, alarm_band, value, threshold, priority, band_message

Asset Domain (6)

StreamWhen It FiresKey Fields
AssetDataAsset-level time series data (aggregation results)asset_id, values (key-value), ts
AssetEventAsset event occurs (start, stop, maintenance, etc.)asset_id, event_type, details, ts
AssetAlarmAsset alarm occursasset_id, alarm_band, priority, message
AssetCommandWhen a command is issued to an assetasset_id, command, args
AssetAggregation1-minute / 5-minute / 1-hour asset aggregation resultsasset_id, window, agg_type, values
AssetContextAsset context (metadata) changeasset_id, context_key, before, after
AssetHealthStatus and AssetConnectionStatus are not streams

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)

StreamWhen It FiresKey Fields
CalendarShift start / end / changeshift_id, calendar_id, event, start_time, end_time
WorkOrderWork order lifecycle transitionorder_id, status, prev_status, asset_id, product_id
OEEOEE evaluation result updatedorder_id, asset_id, oee, availability, performance, quality

System Domain (2)

StreamWhen It FiresKey Fields
LogSystem diagnostic messagemodule, level, code, summary, ts
OSPerformanceServer OS resource metrics (periodic)CPU, memory, disk, etc.
There is no Status stream

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.

StreamPurpose
DateTimePeriodically fired time event (trigger for time-based rules)
PingEngine liveness check
TestRule validation

Every stream must be combined with a window before use (as in Point.win:time(1 sec)). If you use FROM Point alone 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

SyntaxMeaning
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

SyntaxMeaning
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

SituationRecommended 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 aggregationwin:time_batch(1 hour)
Comparing the last N eventswin:length(N)
Tracking rate of changewin:length(2) + the prev() function

WHERE Clauses

The same comparison operators as SQL are used.

OperatorExampleMeaning
= != <>tag_id = 'MOTOR-001'Equal / not equal
< <= > >=value > 80Comparison
BETWEENvalue BETWEEN 70 AND 90Range
INtag_id IN ('A','B','C')List membership
LIKEtag_id LIKE 'MOTOR-%'Pattern matching (% wildcard)
IS NULL / IS NOT NULLquality IS NOT NULLNull check
AND OR NOTvalue > 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

FunctionMeaningExample
count(*)Event countSELECT count(*) FROM Point.win:time(10 sec)
sum(field)Sumsum(value)
avg(field)Averageavg(value)
min(field)Minimummin(value)
max(field)Maximummax(value)
median(field)Medianmedian(value)
stddev(field)Standard deviationstddev(value)
variance(field)Variancevariance(value)
first(field)First value in the windowfirst(value)
last(field)Last value in the windowlast(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

FunctionMeaningExample
prev(N, field)Value N steps backvalue - prev(1, value) (difference from the previous value)
prevwindow(field)Array of all previous values in the windowprevwindow(value)
rate(field)Rate of change per unit timerate(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

LIMIT includes 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

KeywordMeaning
every <e>Matches every time e occurs
e1 -> e2e2 occurs after e1 (ordered match)
e1 and e2Both events occur (order irrelevant)
e1 or e2Either one occurs
not ee 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

FunctionMeaning
current_timestampCurrent 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

FunctionMeaning
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

FunctionMeaning
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.

ContextActivation Condition
EVERY_1_MINUTESEvery 1 minute
EVERY_5_MINUTESEvery 5 minutes
EVERY_10_MINUTESEvery 10 minutes
EVERY_30_MINUTESEvery 30 minutes
EVERY_1_HOURSEvery 1 hour
EVERY_3_HOURSEvery 3 hours
EVERY_6_HOURSEvery 6 hours
EVERY_12_HOURSEvery 12 hours
DAY_WORK_TIMEWeekdays 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.


  • 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)