Skip to main content

EQL — Event Query Language Help

🎬 Read EQL statements · 41초 · 2026.08 기준 화면

Table of Contents​


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

Capabilities Vary by Engine

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 · ScreenInternal URLPurpose
Automation > EQL Query — Documentation/query/indexOperators execute EQL on the fly to verify results (exploration/debugging)
Trigger (No left menu) — Documentation/trigger/indexAutomatically publish EQL matching results to MQ topics or storage
Automation > Statement — Documentation/statement/indexRules 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​

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 fahrenheitArithmetic expression result
SELECT CASE WHEN value > 80 THEN 'HIGH' ELSE 'OK' END AS levelCondition 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)​

StreamTrigger PointKey Fields
PointWhenever a tag value is receivedtag_id, value, quality, ts, site_id, asset_id, unit, java_type
PointMapSame as Point, but used with the tag('<id>') function to query multiple tags as key-values(PointMap specific helper)
AlarmWhen a tag alarm occurstag_id, alarm_band, value, threshold, priority, band_message

Asset Domain (6 Types)​

StreamTrigger PointKey Fields
AssetDataAsset-level time series data (aggregation result)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-min/5-min/1-hour asset aggregation resultsasset_id, window, agg_type, values
AssetContextAsset context (meta) changeasset_id, context_key, before, after
AssetHealthStatus and AssetConnectionStatus are not streams

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

StreamTrigger PointKey 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 updateorder_id, asset_id, oee, availability, performance, quality

System Domain (2 Types)​

StreamTrigger PointKey Fields
LogSystem diagnostic messagesmodule, level, code, summary, ts
OSPerformanceServer OS resource metrics (periodic)CPU, Memory, Disk, etc.
There is no Status stream

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.

StreamPurpose
DateTimePeriodically occurring time events (triggers for time-based rules)
PingEngine heartbeat check
TestFor rule validation

All streams must be combined with a window before use (Point.win:time(1 sec)). If only FROM Point is 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​

SyntaxMeaning
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​

SyntaxMeaning
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​

ScenarioRecommended 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 aggregationwin:time_batch(1 hour)
Comparing last N eventswin:length(N)
Tracking change amountswin:length(2) + prev() function

WHERE Clauses​

Uses comparison operators identical to SQL.

OperatorExampleMeaning
= != <>tag_id = 'MOTOR-001'Equality/Inequality
< <= > >=value > 80Comparison
BETWEENvalue BETWEEN 70 AND 90Range
INtag_id IN ('A','B','C')List inclusion
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 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​

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 windowfirst(value)
last(field)Last value in windowlast(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​

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

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

KeywordMeaning
every <e>Match every time e occurs
e1 -> e2e2 occurs after e1 (Sequential match)
e1 and e2Both events occur (Order irrelevant)
e1 or e2Either one occurs
not ee does not occur
timer:within(<duration>)Match only within specified time
timer:interval(<duration>)Wait for specified time

Time and State Functions​

Time Functions​

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

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

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

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


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