EQL — Event Query Language Help
Table of Contents
- Overview
- Where to Use EQL
- Basic Syntax
- Event Streams (14 Types + 3 Internal Types)
- Windows
- WHERE Clauses
- Aggregation Functions
- Grouping and Sorting
- JOIN — Stream Combination
- Pattern Matching
- Time and State Functions
- Variables and Context
- 12 Practical Examples
- Performance Guide
- Frequently Asked Questions
- Related Screens
Overview
Primary Usage Location: Executed directly in the left menu Automation > EQL Query (
/query/index). The same syntax is used in the condition expressions of Advanced Alarm, Trigger/Statement, and Flow.
EQL (Event Query Language) is a proprietary query language developed by the Platform to allow users to query, aggregate, and perform pattern matching on real-time streaming data using SQL-like syntax. While standard SQL operates on static tables, EQL operates on continuously incoming event streams and emits results at every moment.
Operators can implement the following with a single line of EQL:
- Real-time Monitoring — Immediately emit results the moment a tag value exceeds a threshold.
- Asset-level Aggregation — Automatically calculate average/max/min every 5 minutes.
- Pattern Matching — "Alarm if Event B does not occur within 30 seconds after Event A."
- Time Series Join — Compare time-aligned data from two tags.
EQL uses syntax almost identical to SQL but adds the concept of a Window to specify time or length ranges, such as "last N seconds" or "last N events."
This page covers all EQL capabilities executable from the console. However, when CEP boots, it selects one backend, and the configuration varies by backend — some features, such as joins, variables, or context, are only available on specific backends.
Information on which features run where can be found in the CEP — Backends table. For the language itself, see CEP — EQL Language, and for rejected tokens, see Limits and Rejected Tokens.
Since rejected tokens are identified at the time of registration, they will not fail silently.
Where to Use EQL
EQL is used in the following locations within the Platform:
EQL Queries and Statements are located side-by-side in the left menu 자동화 group. There is no menu explicitly named CEP.
| Menu · Screen | Internal URL | Purpose |
|---|---|---|
| Automation > EQL Query — Documentation | /query/index | Operators execute EQL on the fly to verify results (exploration/debugging) |
| Trigger (No left menu) — Documentation | /trigger/index | Automatically publish EQL matching results to MQ topics or storage |
| Automation > Statement — Documentation | /statement/index | Rules registered by asset (state/aggregation/event/command). Must provide required output fields by type to save |
| Alarm → EQL Alarm | — | Issue alarms based on arbitrary EQL rule conditions |
| Flow → Script | — | EQL expression mode for 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 does not modify data. To issue an alarm as a matching result or call a Flow action node, please connect the result via CEP → Trigger or 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)
Emit all tag points received during the last 1 second, every 1 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 | Arithmetic expression result |
SELECT CASE WHEN value > 80 THEN 'HIGH' ELSE 'OK' END AS level | Condition expression |
Comments
-- 한 줄 주석
/* 여러 줄
주석 */
SELECT * FROM Point.win:time(1 sec)
Event Streams (14 Types + 3 Internal Types)
The following streams (event sources) can be used in the FROM clause of EQL. All streams are automatically generated by the Platform and are available for immediate use without separate registration.
If a name not listed here is used in
FROM, the rule will not be deployed. Only registered event types can become streams (plantpulse.core.engine.eql.EventTypes).
Tag Domain (3 Types)
| Stream | Trigger Point | Key Fields |
|---|---|---|
| Point | Whenever a tag value is received | tag_id, value, quality, ts, site_id, asset_id, unit, java_type |
| PointMap | Same as Point, but used with the tag('<id>') function to query multiple tags as key-values | (PointMap specific helper) |
| Alarm | When a tag alarm occurs | tag_id, alarm_band, value, threshold, priority, band_message |
Asset Domain (6 Types)
| Stream | Trigger Point | Key Fields |
|---|---|---|
| AssetData | Asset-level time series data (aggregation result) | 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-min/5-min/1-hour asset aggregation results | asset_id, window, agg_type, values |
| AssetContext | Asset context (meta) change | asset_id, context_key, before, after |
Previous documentation listed these two as asset domain streams, but they are not registered event types.
If you write FROM AssetHealthStatus..., the rule will not be deployed.
Health and connection status evaluations run as periodic execution jobs, not streams
(AssetHealthStatusDeployer / AssetConnectionStatusDeployer). Those results are reported via screens and APIs. To capture state changes in EQL, detect the lack of receipt of the source AssetEvent · AssetAlarm or Point using a not pattern.
Production Domain (3 Types)
| Stream | Trigger Point | 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 update | order_id, asset_id, oee, availability, performance, quality |
System Domain (2 Types)
| Stream | Trigger Point | Key Fields |
|---|---|---|
| Log | System diagnostic messages | module, level, code, summary, ts |
| OSPerformance | Server OS resource metrics (periodic) | CPU, Memory, Disk, etc. |
An event type named Status is not registered. If you intended to capture OPC or Edge connection state changes via EQL, this stream cannot be used — use the method of detecting the lack of receipt of Point via a not pattern.
Internal Streams (3 Types)
These are registered but are not for rule creation. They are used for diagnostics and health checks.
| Stream | Purpose |
|---|---|
| DateTime | Periodically occurring time events (triggers for time-based rules) |
| Ping | Engine heartbeat check |
| Test | For rule validation |
All streams must be combined with a window before use (
Point.win:time(1 sec)). If onlyFROM Pointis used without a window, the result will disappear immediately and not be visible.
Windows
A window is a "segment of time or length cut from a stream." EQL supports two types of windows.
Time Window
| Syntax | Meaning |
|---|---|
Point.win:time(1 sec) | All events received during 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) | Last 100 events |
Point.win:length(1000) | Last 1,000 events |
Batch Window
win:time_batch / win:length_batch — Emit only once at the moment the window is full or the time expires. Reduces resource usage.
-- 매 1분마다 한 번씩 그 1분간 평균을 emit
SELECT avg(value) FROM Point.win:time_batch(1 min) WHERE tag_id = 'MOTOR-001.TEMP'
Window Selection Guide
| Scenario | Recommended Window |
|---|---|
| Real-time monitoring (Viewing values themselves) | 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 last N events | win:length(N) |
| Tracking change amounts | win:length(2) + prev() function |
WHERE Clauses
Uses comparison operators identical to SQL.
| Operator | Example | Meaning |
|---|---|---|
= != <> | tag_id = 'MOTOR-001' | Equality/Inequality |
< <= > >= | value > 80 | Comparison |
BETWEEN | value BETWEEN 70 AND 90 | Range |
IN | tag_id IN ('A','B','C') | List inclusion |
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 entered twice (\\.).
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
Aggregation Functions
Aggregates events within a window.
Basic Aggregation
| 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 window | first(value) |
last(field) | Last value in window | last(value) |
DISTINCT Aggregation
SELECT count(distinct tag_id) AS unique_tags
FROM Point.win:time(1 min)
Combining 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 ago | value - prev(1, value) (Difference from previous value) |
prevwindow(field) | Array of all previous values in window | prevwindow(value) |
rate(field) | Rate of change per unit time | rate(value) |
Grouping and Sorting
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 — Post-Aggregation Filter
-- 평균 > 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 results at the time of emission.
JOIN — Stream Combination
Two or more streams can be joined by time alignment.
-- 같은 자산에서 같은 시간대의 온도와 압력을 한 행으로
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
Asset Event + Work Order Join
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 occur only while the time windows of the two streams overlap. When comparing data with different time units, align them to a similar cycle using
win:time_batch.
Pattern Matching
A powerful EQL feature — define sequences such as "Event 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 abnormal patterns where a stop occurs immediately after a short operation.
not — Does Not Occur for N Hours
SELECT * FROM pattern [
every a = AssetEvent(event_type = 'STARTUP')
-> ( timer:interval(30 min)
and not AssetData(asset_id = a.asset_id) )
]
Matches if no data is received for 30 minutes after a start (detects non-responsive assets).
and / or — Simultaneous or Either One
SELECT * FROM pattern [
( every AssetAlarm(priority = 'ERROR') )
and
( every WorkOrder(status = 'START') )
]
Key Keywords for Pattern Matching
| Keyword | Meaning |
|---|---|
every <e> | Match every time e occurs |
e1 -> e2 | e2 occurs after e1 (Sequential match) |
e1 and e2 | Both events occur (Order irrelevant) |
e1 or e2 | Either one occurs |
not e | e does not occur |
timer:within(<duration>) | Match only within specified time |
timer:interval(<duration>) | Wait for 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) | Extract timestamp of event |
hour_of_day(ts) | Hour (0~23) |
day_of_week(ts) | Day of week (1(Sun)~7(Sat)) |
minute_of_hour(ts) | Minute (0~59) |
dayofmonth(ts) | Day of month (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>') | Most recent value of that tag (PointMap context) |
prev_status(asset_id) | Previous state of asset |
health(asset_id) | Asset health state (OK/WARN/ERROR/UNKNOWN) |
is_in_shift(asset_id) | Whether asset is within shift |
Arithmetic and String Functions
| Function | Meaning |
|---|---|
abs(x) / round(x, n) / floor(x) / ceil(x) | Mathematics |
pow(x, y) | x 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, ...) | Concatenate |
Variables and Context
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 all EQL rules will immediately use the new value.
Context — Time/Condition Based Activation
The Platform has commonly used contexts predefined for you to use.
| 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 detection of threshold excess
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 (emit every 5 min)
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 — Amount of change (compared to 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 only during night shift
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 — Low OEE for work order
SELECT order_id, asset_id, oee, availability, performance, quality
FROM OEE.win:time(1 sec)
WHERE oee < 0.6
Example 8 — Detection of asset with no response for 30 minutes
SELECT * FROM pattern [
every a = AssetData()
-> ( timer:interval(30 min)
and not AssetData(asset_id = a.asset_id) )
]
Example 9 — Stop 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 — Cumulative production per hour by line unit
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 with most alarms
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. Intentionally keep window sizes 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. Place WHERE clauses close to the trigger
Place static filters such as tag ID, asset ID, and site ID at the very beginning of the WHERE clause. They are evaluated before subsequent GROUP BY/HAVING operations.
3. Double-sided wildcards in LIKE '%...%' are costly
-- ❌ 나쁜 예
WHERE tag_id LIKE '%TEMP%'
-- ✅ 좋은 예
WHERE tag_id LIKE '%.TEMP' -- 접미사 매칭만
4. Use pattern matching with short windows
Do not omit the time limiter of pattern [ ... timer:within(<n-minutes>) ]. Infinite patterns will continuously occupy memory.
5. Adjust result emission frequency
By using a context EVERY_N_MINUTES context, you can evaluate EQL itself every 1 second but separate it so that it emits only every N minutes, reducing downstream load.
Frequently Asked Questions
Q. I entered an EQL query, but nothing appears on the results screen.
A. Please check the following: ① Did you use only FROM Point — a window is required (Point.win:time(1 sec)). ② Is the WHERE condition too narrow, resulting in no matches? ③ Did you click the ▶ Start button in the top right? ④ Is the results panel in a ⏸ Pause state?
Q. How is this different from SQL?
A. The biggest difference is the Window concept. Since EQL operates on an infinite stream, a "range" must be specified. Other features not found in SQL include pattern [ ... ] sequence matching and context time contexts.
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 every time an event arrives. win:time_batch(1 sec) is tumbling — events are bundled in 1-second increments and emitted once at the moment the second expires. Use batch if you need results exactly every second.
Q. What is the difference between pattern [ ... ] and general SELECT ... WHERE ...?
A. SELECT evaluates by looking at a single event (or window), whereas pattern evaluates an event sequence (e.g., B after A, B without A). Use pattern if time order or causality is important.
Q. Are registered variables reflected immediately?
A. Yes. When you change the value of a variable defined via create variable on the screen, all deployed EQL rules will use the new value from the next evaluation.
Q. How do I send EQL matching results as an alarm? A. Register the EQL in CEP → Trigger and enable "MQ Streaming" or "Storage Save" options, or register the EQL in Alarm → EQL Alarm to automatically issue an alarm upon matching.
Q. Can I apply an asset-level rule to all assets collectively? A. Define an asset-level EQL rule in CEP → Statement, and map that rule to an asset category in the model/ontology. It will be automatically deployed to all assets in the same category and applied automatically whenever new assets are added.
Q. Can I handle two streams simultaneously in one line of EQL?
A. Yes. As shown in the JOIN section above, list the two streams separated by commas in the FROM clause and write the join condition using WHERE.
Q. Too many results are being produced.
A. Use LIMIT N to receive only the top N, use context EVERY_N_MINUTES to increase the emission cycle, or use win:time_batch to bundle them into a batch window. Making the WHERE clause more strict is also effective.
Q. Where can I practice writing EQL? A. The CEP → Query screen is the EQL practice tool. Enter a line and click ▶ Run to see immediate results. Try copying from the 12 Practical Examples above.
Related Screens
- CEP — Screen for writing EQL queries/triggers/statements
- Alarm → EQL Alarm — Issuing alarms with arbitrary conditions via EQL
- Flow — EQL mode for
flow_script_filter/flow_script_transform/flow_switch - Tag Form — AI Tab — EQL conditions for prediction/anomaly detection rules
- Integrated Domain ID Search — Quick lookup of tag/asset IDs for use in EQL (Ctrl+K)