> For the complete documentation index, see [llms.txt](https://kopens.gitbook.io/plantpulse-platform/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://kopens.gitbook.io/plantpulse-platform/user/eql.md).

# EQL 도움말

## 목차

* [개요](#overview)
* [어디에서 EQL 을 사용하나](#where-used)
* [기본 문법](#basic-syntax)
* [이벤트 스트림 (16종)](#streams)
* [윈도우](#windows)
* [WHERE 조건](#where-clauses)
* [집계 함수](#aggregations)
* [그룹화·정렬](#group-order)
* [JOIN — 스트림 결합](#joins)
* [패턴 매칭](#patterns)
* [시간·상태 함수](#time-functions)
* [변수와 컨텍스트](#variables-contexts)
* [실전 예제 12 선](#examples)
* [성능 가이드](#performance)
* [자주 묻는 질문](#faq)
* [관련 화면](#related)

***

## 개요 <a href="#overview" id="overview"></a>

EQL (Event Query Language) 은 플랜트펄스 플랫폼이 실시간 스트리밍 데이터를 SQL 문법처럼 질의·집계·패턴 매칭할 수 있도록 만든 자체 쿼리 언어입니다. 일반 SQL 이 정적 테이블을 대상으로 동작한다면, EQL 은 **계속해서 들어오는 이벤트 스트림** 위에서 동작하며 매 순간 결과를 emit 합니다.

운영자는 EQL 한 줄로 다음을 구현하실 수 있습니다.

* **실시간 모니터링** — 태그 값이 임계 초과한 순간 즉시 결과 emit
* **자산 단위 집계** — 매 5분 평균/최대/최소 자동 계산
* **패턴 매칭** — "A 이벤트 후 30초 안에 B 이벤트가 안 오면 알람"
* **시계열 조인** — 두 태그의 시간 정렬 비교

EQL 은 SQL 과 거의 동일한 문법을 사용하지만 **윈도우(Window)** 개념이 추가되어 "최근 N초 / N건" 같은 시간/길이 범위를 명시합니다.

***

## 어디에서 EQL 을 사용하나 <a href="#where-used" id="where-used"></a>

플랫폼 내 EQL 사용 위치는 다음과 같습니다.

| 화면                                                              | 용도                                                                          |
| --------------------------------------------------------------- | --------------------------------------------------------------------------- |
| [CEP → 쿼리](/plantpulse-platform/user/cep.md#query)              | 운영자가 즉석에서 EQL 을 실행해 결과를 확인 (탐색·디버깅)                                         |
| [CEP → 트리거](/plantpulse-platform/user/cep.md#trigger)           | EQL 매칭 결과를 MQ 토픽 또는 스토리지로 자동 발행                                             |
| [CEP → 스테이트먼트](/plantpulse-platform/user/cep.md#statement)      | 자산 단위로 등록된 룰 (상태/집계/이벤트/명령)                                                 |
| [알람 → EQL 알람](/plantpulse-platform/user/alarm.md#config)        | EQL 룰로 임의 조건 알람 발행                                                          |
| [플로우 → 스크립트](/plantpulse-platform/user/flow.md#scripting)       | `flow_script_filter` / `flow_script_transform` / `flow_switch` 의 EQL 표현식 모드 |
| [태그 양식 — 인공지능 탭](/plantpulse-platform/user/factory.md#tag-view) | 예측·이상감지 룰의 입력 조건                                                            |

> EQL 은 **읽기 전용** 입니다 — 데이터를 변경하지 않습니다. 매칭 결과로 알람을 발행하거나 [플로우](/plantpulse-platform/user/flow.md) 액션 노드를 호출하려면 [CEP → 트리거](/plantpulse-platform/user/cep.md#trigger) 또는 [EQL 알람](/plantpulse-platform/user/alarm.md#config) 으로 결과를 연결해 주세요.

***

## 기본 문법 <a href="#basic-syntax" id="basic-syntax"></a>

EQL 의 기본 구조는 SQL 과 거의 동일합니다.

```sql
SELECT <컬럼 또는 함수>
FROM   <스트림>.<윈도우>
WHERE  <조건>
GROUP  BY <필드>
HAVING <집계 조건>
ORDER  BY <필드> [ASC|DESC]
```

가장 단순한 EQL:

```sql
SELECT * FROM Point.win:time(1 sec)
```

> 최근 **1초 동안** 들어온 모든 태그 포인트를 1초마다 emit.

### 와일드카드와 컬럼 선택

| 구문                                                               | 의미           |
| ---------------------------------------------------------------- | ------------ |
| `SELECT *`                                                       | 모든 컬럼 포함     |
| `SELECT tag_id, value`                                           | 특정 컬럼만       |
| `SELECT value AS v, tag_id AS id`                                | 별칭(alias) 부여 |
| `SELECT value * 9 / 5 + 32 AS fahrenheit`                        | 산술식 결과       |
| `SELECT CASE WHEN value > 80 THEN 'HIGH' ELSE 'OK' END AS level` | 조건식          |

### 주석

```sql
-- 한 줄 주석
/* 여러 줄
   주석 */
SELECT * FROM Point.win:time(1 sec)
```

***

## 이벤트 스트림 (16종) <a href="#streams" id="streams"></a>

EQL 의 `FROM` 절에 올 수 있는 스트림(이벤트 소스)은 다음과 같습니다. 모든 스트림은 플랫폼이 자동으로 발생시키며, 별도 등록 없이 즉시 사용 가능합니다.

### 태그 도메인 (3종)

| 스트림          | 발생 시점                                          | 주요 필드                                                                          |
| ------------ | ---------------------------------------------- | ------------------------------------------------------------------------------ |
| **Point**    | 태그 값이 수신될 때마다                                  | `tag_id`, `value`, `quality`, `ts`, `site_id`, `asset_id`, `unit`, `java_type` |
| **PointMap** | 동일하지만 `tag('<id>')` 함수와 함께 사용해 다중 태그를 키-값으로 조회 | (PointMap 전용 헬퍼)                                                               |
| **Alarm**    | 태그 알람이 발생할 때                                   | `tag_id`, `alarm_band`, `value`, `threshold`, `priority`, `band_message`       |

### 자산 도메인 (8종)

| 스트림                       | 발생 시점                  | 주요 필드                                                                          |
| ------------------------- | ---------------------- | ------------------------------------------------------------------------------ |
| **AssetData**             | 자산 단위 시계열 데이터 (집계 결과)  | `asset_id`, `values`(key-value), `ts`                                          |
| **AssetEvent**            | 자산 이벤트 발생 (시작·정지·정비 등) | `asset_id`, `event_type`, `details`, `ts`                                      |
| **AssetAlarm**            | 자산 알람 발생               | `asset_id`, `alarm_band`, `priority`, `message`                                |
| **AssetCommand**          | 자산에 명령이 발행될 때          | `asset_id`, `command`, `args`                                                  |
| **AssetAggregation**      | 1분/5분/1시간 자산 집계 결과     | `asset_id`, `window`, `agg_type`, `values`                                     |
| **AssetContext**          | 자산 컨텍스트(메타) 변경         | `asset_id`, `context_key`, `before`, `after`                                   |
| **AssetHealthStatus**     | 자산 헬스 평가 (주기)          | `asset_id`, `status`, `info_count`, `warn_count`, `error_count`, `prev_status` |
| **AssetConnectionStatus** | 자산 연결 평가 (주기)          | `asset_id`, `status`, `disconnected_count`, `prev_status`                      |

### 생산 도메인 (3종)

| 스트림           | 발생 시점          | 주요 필드                                                                   |
| ------------- | -------------- | ----------------------------------------------------------------------- |
| **Calendar**  | 시프트 시작/종료/변경   | `shift_id`, `calendar_id`, `event`, `start_time`, `end_time`            |
| **WorkOrder** | 워크오더 라이프사이클 전이 | `order_id`, `status`, `prev_status`, `asset_id`, `product_id`           |
| **OEE**       | OEE 평가 결과 갱신   | `order_id`, `asset_id`, `oee`, `availability`, `performance`, `quality` |

### 시스템 도메인 (2종)

| 스트림        | 발생 시점        | 주요 필드                                                                            |
| ---------- | ------------ | -------------------------------------------------------------------------------- |
| **Log**    | 시스템 진단 메시지   | `module`, `level`, `code`, `summary`, `ts`                                       |
| **Status** | OPC/엣지 상태 변경 | `entity_type`(OPC/EDGE), `id`, `connection_status`, `scan_status`, `prev_status` |

> 모든 스트림은 사용 전 **반드시 윈도우와 결합**해야 합니다 (`Point.win:time(1 sec)` 처럼). 윈도우 없이 `FROM Point` 만 쓰면 결과가 즉시 사라져 보이지 않습니다.

***

## 윈도우 <a href="#windows" id="windows"></a>

윈도우는 "스트림 위에서 잘라낸 시간 또는 길이의 구간"입니다. EQL 은 두 종류의 윈도우를 지원합니다.

### 시간 윈도우 (Time Window)

| 문법                       | 의미                                |
| ------------------------ | --------------------------------- |
| `Point.win:time(1 sec)`  | 최근 **1초** 동안 들어온 모든 이벤트 (sliding) |
| `Point.win:time(10 sec)` | 최근 10초                            |
| `Point.win:time(5 min)`  | 최근 5분                             |
| `Point.win:time(1 hour)` | 최근 1시간                            |

지원되는 시간 단위: `sec`(초) · `min`(분) · `hour`(시간) · `day`(일).

### 길이 윈도우 (Length Window)

| 문법                       | 의미               |
| ------------------------ | ---------------- |
| `Point.win:length(100)`  | 최근 **100건**의 이벤트 |
| `Point.win:length(1000)` | 최근 1,000건        |

### 배치 윈도우 (Batch Window)

`win:time_batch` / `win:length_batch` — 윈도우가 가득 차거나 시간이 만료된 **순간에만** 한 번 emit. 사용량 자원 절감.

```sql
-- 매 1분마다 한 번씩 그 1분간 평균을 emit
SELECT avg(value) FROM Point.win:time_batch(1 min) WHERE tag_id = 'MOTOR-001.TEMP'
```

### 윈도우 선택 가이드

| 상황                 | 추천 윈도우                        |
| ------------------ | ----------------------------- |
| 실시간 모니터링 (값 자체 보기) | `win:time(1 sec)`             |
| 단기 평균/최대 (5분 평균 등) | `win:time(5 min)`             |
| 시간 단위 집계           | `win:time_batch(1 hour)`      |
| 최근 N건 비교           | `win:length(N)`               |
| 변화량 추적             | `win:length(2)` + `prev()` 함수 |

***

## WHERE 조건 <a href="#where-clauses" id="where-clauses"></a>

SQL 과 동일한 비교 연산자를 사용합니다.

| 연산자                       | 예시                                | 의미                |
| ------------------------- | --------------------------------- | ----------------- |
| `=` `!=` `<>`             | `tag_id = 'MOTOR-001'`            | 같음/다름             |
| `<` `<=` `>` `>=`         | `value > 80`                      | 비교                |
| `BETWEEN`                 | `value BETWEEN 70 AND 90`         | 범위                |
| `IN`                      | `tag_id IN ('A','B','C')`         | 목록 포함             |
| `LIKE`                    | `tag_id LIKE 'MOTOR-%'`           | 패턴 매칭 (`%` 와일드카드) |
| `IS NULL` / `IS NOT NULL` | `quality IS NOT NULL`             | null 여부           |
| `AND` `OR` `NOT`          | `value > 80 AND quality = 'GOOD'` | 논리 결합             |

### 정규식

```sql
SELECT * FROM Point.win:time(1 sec)
WHERE tag_id REGEXP '.*\\.TEMP$'
```

> 모든 끝이 `.TEMP` 인 태그만 매칭. 백슬래시는 두 번 (`\\.`) 입력.

### 동적 사이트/자산 필터

```sql
SELECT * FROM Point.win:time(1 sec)
WHERE site_id = 'SITE-A'
  AND asset_id LIKE 'LINE-1.%'
  AND value > 75
```

***

## 집계 함수 <a href="#aggregations" id="aggregations"></a>

윈도우 내의 이벤트들을 집계합니다.

### 기본 집계

| 함수                | 의미        | 예시                                            |
| ----------------- | --------- | --------------------------------------------- |
| `count(*)`        | 이벤트 건수    | `SELECT count(*) FROM Point.win:time(10 sec)` |
| `sum(field)`      | 합계        | `sum(value)`                                  |
| `avg(field)`      | 평균        | `avg(value)`                                  |
| `min(field)`      | 최솟값       | `min(value)`                                  |
| `max(field)`      | 최댓값       | `max(value)`                                  |
| `median(field)`   | 중앙값       | `median(value)`                               |
| `stddev(field)`   | 표준편차      | `stddev(value)`                               |
| `variance(field)` | 분산        | `variance(value)`                             |
| `first(field)`    | 윈도우 처음 값  | `first(value)`                                |
| `last(field)`     | 윈도우 마지막 값 | `last(value)`                                 |

### DISTINCT 집계

```sql
SELECT count(distinct tag_id) AS unique_tags
FROM Point.win:time(1 min)
```

### CASE 와 결합

```sql
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'
```

### 시계열 함수

| 함수                  | 의미               | 예시                                  |
| ------------------- | ---------------- | ----------------------------------- |
| `prev(N, field)`    | N 단계 이전 값        | `value - prev(1, value)` (직전 값과 차이) |
| `prevwindow(field)` | 윈도우 안 모든 이전 값 배열 | `prevwindow(value)`                 |
| `rate(field)`       | 단위 시간당 변화율       | `rate(value)`                       |

***

## 그룹화·정렬 <a href="#group-order" id="group-order"></a>

### GROUP BY

```sql
-- 자산별 최근 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 — 집계 후 필터

```sql
-- 평균 > 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

```sql
-- 가장 뜨거운 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` 은 결과 emit 시점의 가장 상위 N개만 포함합니다.

***

## JOIN — 스트림 결합 <a href="#joins" id="joins"></a>

두 개 이상의 스트림을 시간 정렬해 조인할 수 있습니다.

```sql
-- 같은 자산에서 같은 시간대의 온도와 압력을 한 행으로
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
```

### 자산 이벤트 + 워크오더 조인

```sql
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'
```

> 두 스트림의 시간 윈도우가 겹치는 동안만 조인 결과가 발생합니다. 시간 단위가 다른 데이터를 비교할 때는 `win:time_batch` 로 비슷한 주기에 맞춰 주세요.

***

## 패턴 매칭 <a href="#patterns" id="patterns"></a>

EQL 의 강력한 기능 — "이벤트 A 이후 N초 안에 B 가 (안) 일어남" 같은 시퀀스를 정의할 수 있습니다.

### `every` — 매번 매칭

```sql
SELECT * FROM pattern [
  every  a = AssetEvent(event_type = 'STARTUP')
         -> b = AssetEvent(event_type = 'SHUTDOWN', asset_id = a.asset_id)
              where timer:within(60 sec)
]
```

> **시작 이벤트 → 60초 안에 정지 이벤트**의 쌍을 매번 매칭. 짧은 가동 후 즉시 정지하는 비정상 패턴 탐지.

### `not` — N 시간 동안 안 일어남

```sql
SELECT * FROM pattern [
  every  a = AssetEvent(event_type = 'STARTUP')
         -> ( timer:interval(30 min)
              and not AssetData(asset_id = a.asset_id) )
]
```

> 시작 후 30분 동안 데이터가 들어오지 않으면 매칭 (응답 없는 자산 탐지).

### `and` / `or` — 동시 또는 둘 중 하나

```sql
SELECT * FROM pattern [
  ( every AssetAlarm(priority = 'ERROR') )
    and
  ( every WorkOrder(status = 'START') )
]
```

### 패턴 매칭의 핵심 키워드

| 키워드                          | 의미                         |
| ---------------------------- | -------------------------- |
| `every <e>`                  | `e` 가 발생할 때마다 매칭           |
| `e1 -> e2`                   | `e1` 다음에 `e2` 가 발생 (순서 매칭) |
| `e1 and e2`                  | 두 이벤트가 모두 발생 (순서 무관)       |
| `e1 or e2`                   | 둘 중 하나 발생                  |
| `not e`                      | `e` 가 발생하지 않음              |
| `timer:within(<duration>)`   | 지정 시간 내에만 매칭               |
| `timer:interval(<duration>)` | 지정 시간 만큼 대기                |

***

## 시간·상태 함수 <a href="#time-functions" id="time-functions"></a>

### 시간 함수

| 함수                   | 의미                 |
| -------------------- | ------------------ |
| `current_timestamp`  | 현재 시각 (ms)         |
| `current_date()`     | 오늘 날짜 (yyyy-MM-dd) |
| `timestamp(field)`   | 이벤트의 timestamp 추출  |
| `hour_of_day(ts)`    | 0\~23 시            |
| `day_of_week(ts)`    | 1(일)\~7(토)         |
| `minute_of_hour(ts)` | 0\~59              |
| `dayofmonth(ts)`     | 1\~31              |

```sql
-- 야간 (22시~6시) 발생 알람만
SELECT * FROM Alarm.win:time(1 hour)
WHERE hour_of_day(ts) >= 22 OR hour_of_day(ts) < 6
```

### 상태 함수

| 함수                      | 의미                                       |
| ----------------------- | ---------------------------------------- |
| `tag('<tag_id>')`       | 그 태그의 가장 최근 값 (PointMap 컨텍스트)            |
| `prev_status(asset_id)` | 자산의 직전 상태                                |
| `health(asset_id)`      | 자산 헬스 상태 (`OK`/`WARN`/`ERROR`/`UNKNOWN`) |
| `is_in_shift(asset_id)` | 자산이 시프트 내부에 있는지                          |

### 산술·문자열 함수

| 함수                                                | 의미      |
| ------------------------------------------------- | ------- |
| `abs(x)` / `round(x, n)` / `floor(x)` / `ceil(x)` | 수학      |
| `pow(x, y)`                                       | x의 y 제곱 |
| `sqrt(x)`                                         | 제곱근     |
| `length(s)`                                       | 문자열 길이  |
| `upper(s)` / `lower(s)`                           | 대소 변환   |
| `substring(s, start, len)`                        | 부분 문자열  |
| `concat(a, b, ...)`                               | 연결      |

***

## 변수와 컨텍스트 <a href="#variables-contexts" id="variables-contexts"></a>

### 변수 — 동적 임계값

```sql
-- 변수 정의 (운영자가 화면에서 조정 가능)
create variable double max_temp = 80;

-- 변수 사용
SELECT * FROM Point.win:time(1 sec)
WHERE tag_id LIKE '%.TEMP' AND value > max_temp
```

변수 값은 운영 중 변경할 수 있으며 모든 EQL 룰이 즉시 새 값을 사용합니다.

### 컨텍스트 — 시간/조건 기반 활성화

플랫폼에는 **자주 쓰는 컨텍스트가 미리 정의**되어 있어 그대로 가져다 쓰실 수 있습니다.

| 컨텍스트               | 활성 조건           |
| ------------------ | --------------- |
| `EVERY_1_MINUTES`  | 매 1분            |
| `EVERY_5_MINUTES`  | 매 5분            |
| `EVERY_10_MINUTES` | 매 10분           |
| `EVERY_30_MINUTES` | 매 30분           |
| `EVERY_1_HOURS`    | 매 1시간           |
| `EVERY_3_HOURS`    | 매 3시간           |
| `EVERY_6_HOURS`    | 매 6시간           |
| `EVERY_12_HOURS`   | 매 12시간          |
| `DAY_WORK_TIME`    | 평일 09:00\~18:00 |

```sql
-- 5분마다 한 번씩 평균 온도 emit (그 사이에는 결과가 안 옴)
context EVERY_5_MINUTES
SELECT avg(value) AS avg_temp
FROM Point.win:time(5 min)
WHERE tag_id = 'MOTOR-001.TEMP'
```

```sql
-- 주간 근무시간에만 평가
context DAY_WORK_TIME
SELECT * FROM AssetAlarm.win:time(1 sec)
WHERE priority = 'ERROR'
```

***

## 실전 예제 12 선 <a href="#examples" id="examples"></a>

### 예제 1 — 실시간 한 태그 모니터링

```sql
SELECT value, ts, quality
FROM Point.win:time(1 sec)
WHERE tag_id = 'MOTOR-001.SPEED'
```

### 예제 2 — 임계 초과 즉시 감지

```sql
SELECT tag_id, value, ts
FROM Point.win:time(1 sec)
WHERE tag_id LIKE '%.TEMP' AND value > 90
```

### 예제 3 — 자산별 5분 평균 (5분마다 emit)

```sql
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
```

### 예제 4 — 변화량 (직전 값 대비)

```sql
SELECT tag_id, value, value - prev(1, value) AS delta
FROM   Point.win:length(2)
WHERE  tag_id = 'TANK-001.LEVEL'
```

### 예제 5 — 야간 시프트의 ERROR 알람만

```sql
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
```

### 예제 6 — 자산 헬스 전이 (OK → WARN)

```sql
SELECT asset_id, status, prev_status, ts
FROM   AssetHealthStatus.win:time(1 sec)
WHERE  prev_status = 'OK' AND status = 'WARN'
```

### 예제 7 — 워크오더 OEE 저조

```sql
SELECT order_id, asset_id, oee, availability, performance, quality
FROM   OEE.win:time(1 sec)
WHERE  oee < 0.6
```

### 예제 8 — 30 분 응답 없는 자산 감지

```sql
SELECT * FROM pattern [
  every a = AssetData()
        -> ( timer:interval(30 min)
             and not AssetData(asset_id = a.asset_id) )
]
```

### 예제 9 — 시작 후 곧바로 정지 (불안정 가동)

```sql
SELECT * FROM pattern [
  every  a = AssetEvent(event_type = 'STARTUP')
         -> b = AssetEvent(event_type = 'SHUTDOWN', asset_id = a.asset_id)
              where timer:within(60 sec)
]
```

### 예제 10 — 다중 태그 동시 비교 (PointMap)

```sql
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)
```

### 예제 11 — 라인 단위 시간당 누적 생산량

```sql
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
```

### 예제 12 — TOP 10 알람 발생 자산

```sql
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
```

***

## 성능 가이드 <a href="#performance" id="performance"></a>

### 1. 윈도우 크기를 의도적으로 작게

```sql
-- ❌ 나쁜 예 — 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. WHERE 절을 트리거 가까이

태그 ID·자산 ID·사이트 ID 같은 정적 필터는 **WHERE 절 가장 앞**에 두세요. 후속 GROUP BY/HAVING 보다 먼저 평가됩니다.

### 3. `LIKE '%...%'` 양면 와일드카드는 비용 큼

```sql
-- ❌ 나쁜 예
WHERE tag_id LIKE '%TEMP%'

-- ✅ 좋은 예
WHERE tag_id LIKE '%.TEMP'   -- 접미사 매칭만
```

### 4. 패턴 매칭은 짧은 윈도우와 함께

`pattern [ ... timer:within(<수 분>) ]` 의 시간 한정자를 빠뜨리지 마세요. 무한대 패턴은 메모리를 계속 점유합니다.

### 5. 결과 emit 빈도 조정

`context EVERY_N_MINUTES` 컨텍스트로 EQL 자체 평가는 1초 단위로 하되, **emit 만 N분마다** 하도록 분리하면 다운스트림 부하가 줄어듭니다.

***

## 자주 묻는 질문 <a href="#faq" id="faq"></a>

**Q. EQL 쿼리를 입력했는데 결과 화면에 아무 것도 안 나옵니다.** A. 다음을 확인해 주세요. ① `FROM Point` 만 쓰지 않았는지 — 윈도우 필요 (`Point.win:time(1 sec)`). ② WHERE 조건이 너무 좁아 매칭이 안 되는 것은 아닌지. ③ 우상단 ▶ 시작 버튼을 눌렀는지. ④ 결과 패널이 ⏸ 중지 상태가 아닌지.

**Q. SQL 과 무엇이 다른가요?** A. 가장 큰 차이는 **윈도우** 개념입니다. EQL 은 무한 스트림 위에서 동작하므로 "어느 범위" 를 명시해야 합니다. 그 외 `pattern [ ... ]` 시퀀스 매칭과 `context` 시간 컨텍스트는 SQL 에 없는 기능입니다.

**Q. `win:time(1 sec)` 와 `win:time_batch(1 sec)` 의 차이는?** A. `win:time(1 sec)` 는 **sliding** — 매 이벤트가 들어올 때마다 결과 갱신. `win:time_batch(1 sec)` 는 **tumbling** — 1초 단위로 묶어 1초가 만료되는 순간에만 한 번 emit. 정확히 1초마다 결과가 필요하면 batch 를 쓰세요.

**Q. `pattern [ ... ]` 와 일반 `SELECT ... WHERE ...` 의 차이는?** A. `SELECT` 는 한 이벤트(또는 윈도우)를 보고 평가하지만, `pattern` 은 **이벤트 시퀀스** (A 다음 B, A 없이 B 등)를 평가합니다. 시간 순서·인과관계가 중요하면 pattern 을 쓰세요.

**Q. 등록한 변수가 즉시 반영되나요?** A. 네. `create variable` 로 정의한 변수의 값을 화면에서 변경하면 이미 배포된 모든 EQL 룰이 다음 평가부터 새 값을 사용합니다.

**Q. EQL 매칭 결과를 알람으로 보내려면?** A. [CEP → 트리거](/plantpulse-platform/user/cep.md#trigger) 에 EQL 을 등록한 뒤 "MQ 스트리밍" 또는 "스토리지 저장" 옵션을 켜시거나, [알람 → EQL 알람](/plantpulse-platform/user/alarm.md#config) 에 EQL 을 등록하시면 매칭 시 알람이 자동 발행됩니다.

**Q. 자산 단위 룰을 모든 자산에 일괄 적용할 수 있나요?** A. [CEP → 스테이트먼트](/plantpulse-platform/user/cep.md#statement) 에서 자산 단위 EQL 룰을 정의하고, 모델/온톨로지에서 그 룰을 자산 카테고리에 매핑하면 같은 카테고리의 모든 자산에 자동 배포됩니다. 자산이 추가될 때마다 자동으로 적용됩니다.

**Q. EQL 한 줄에서 두 스트림을 동시에 다룰 수 있나요?** A. 네. 위 [JOIN](#joins) 섹션의 예제처럼 두 스트림을 `FROM` 절에 콤마로 나열하고 `WHERE` 로 조인 조건을 작성하면 됩니다.

**Q. 결과가 너무 많이 나옵니다.** A. `LIMIT N` 으로 상위 N개만 받으시거나, `context EVERY_N_MINUTES` 로 emit 주기를 늘리시거나, `win:time_batch` 를 사용해 배치 윈도우로 묶어 주세요. WHERE 절을 더 엄격하게 잡는 것도 효과적입니다.

**Q. EQL 작성을 어디서 연습하나요?** A. [CEP → 쿼리](/plantpulse-platform/user/cep.md#query) 화면이 EQL 실습 도구입니다. 한 줄을 입력하고 ▶ 실행하면 즉시 결과를 받아볼 수 있습니다. 위 [실전 예제 12 선](#examples) 부터 복사해 보세요.

***

## 관련 화면 <a href="#related" id="related"></a>

* [CEP](/plantpulse-platform/user/cep.md) — EQL 쿼리/트리거/스테이트먼트 작성 화면
* [알람 → EQL 알람](/plantpulse-platform/user/alarm.md#config) — EQL 로 임의 조건의 알람 발행
* [플로우](/plantpulse-platform/user/flow.md) — `flow_script_filter` / `flow_script_transform` / `flow_switch` 의 EQL 모드
* [태그 양식 — 인공지능 탭](/plantpulse-platform/user/factory.md#tag-view) — 예측·이상감지 룰의 EQL 조건
* [도메인 ID 통합 검색](/plantpulse-platform/user/login.md#domain-search) — EQL 에 사용할 태그/자산 ID 빠른 조회 (Ctrl+K)
