メインコンテンツまでスキップ

EQL言語

EQL(PlantPulse CEP SQL) は、PlantPulse のストリーミングクエリ言語です。SQL-92 をベースとし、ストリーミングに必要な機能のみを拡張しています。

このページの例はすべてゴールデンコーパス(製品の契約テストで回答が固定されている文章)から引用しています。作成された例は含まれていません。

文章の形式​

[ INSERT INTO <stream> ]
SELECT [DISTINCT] <식> [AS <별칭>] [, ...]
FROM <소스> [ <조인> ... ]
[ WHERE <condition> ]
[ GROUP BY <식> [, ...] ]
[ HAVING <condition> ]
[ OUTPUT LAST|FIRST|ALL|SNAPSHOT EVERY <기간> ]
[ ORDER BY <식> [ASC|DESC] [NULLS FIRST|NULLS LAST] [, ...] ]
[ LIMIT <n> [OFFSET <n>] ]

句の順序は固定です。大文字と小文字は区別しません。

時間期間には «単位が必須» です

1 sec · 30 min · 10 SECONDS のように記述します。先頭の数字は読み込まれません。

このルールには例外があります。単位のない数字を別の単位として読み込んだため、30秒ウィンドウが8時間ウィンドウになった事例があります。

判定は3種類です​

判定意味対応
SUPPORTED言語が読み込み、共通表現として完全に格納されたなし
UNSUPPORTED読み込まれたが その構成が言語の範囲外 である。トークンとして命名されるその構成を除いて書き直す
UNPARSEABLE文法が読み込めなかった。行・列位置が共に提示されるタイポを確認する
«言語が読み込む» ことと «このエンジンが動作する» ことは別の問題です

SUPPORTED である文章であっても、起動したバックエンドが拒否的に処理する場合があります。その際は どの構成が原因であるかがトークンで 表示されます → 制限事項と拒否トークン

どのバックエンドが何を受け付けるかは、バックエンド の表に記載されています。


SQL-92 コア

SELECT · WHERE · GROUP BY​

SELECT site_id, COUNT(*) AS c FROM Point.win:time_batch(1 sec) GROUP BY site_id
SELECT tag_id, value
FROM Point.win:time(1 sec)
WHERE tag_id LIKE '%.TEMP'

集計がある場合、ウィンドウが閉じる際にその内容に基づいて一度だけ計算され、ない場合はイベントごとに1行が出力されます。

CASE · 算術​

SELECT tag_id, latency,
CASE WHEN latency > 1000 THEN 'HIGH'
WHEN latency > 500 THEN 'WARN'
ELSE 'OK' END AS band
FROM Point.win:length(1)
SELECT alarm_config_id, tag_id, asset_id, priority,
off_timestamp - timestamp AS duration_ms
FROM Alarm.win:length(1)
WHERE is_on = 'N'
AND off_timestamp - timestamp > 86400000
ELSE のない CASE はデフォルトのバックエンドが拒否します

expr:case-without-else です。値が残った場合に何を出力するかが記述されていない場合、エンジンごとに異なる回答を返します。

DISTINCT​

SELECT DISTINCT site_id, type FROM Point.win:time_batch(1 sec)
DISTINCT には条件があり、拒否では解消できない不一致が1つあります

GROUP BY · HAVING · 計算された投影 · 既に適用されたウィンドウと共に使用すると 名称が提示され拒否 されます。

また、もう1つの違いがあります。デフォルトバックエンドの重複排除は ストリームの全期間 から削除しますが、SQL の DISTINCT は 結果集合の中 で削除します。無限ストリームにおいて、この2つは同一ではありません。

ORDER BY · LIMIT · OFFSET​

SELECT id, count(*) AS c FROM Point.win:time_batch(1 sec)
GROUP BY id
ORDER BY c DESC
LIMIT 10 OFFSET 5

NULL の位置も文章で定義します — ORDER BY c DESC NULLS LAST。

JOIN​

SELECT p.id, o.name
FROM Point.win:length(10) AS p
INNER JOIN Other.win:length(10) AS o ON p.id = o.id

INNER JOIN と LEFT [OUTER] JOIN を ON 述語と共に使用します。結合は 左から順に処理 されます。

単項 NOT​

SELECT a FROM Point.win:length(10) WHERE NOT a = 1
NOT の «位置» が意味します

比較より上で AND より下です。そのため NOT a = 1 AND b = 2 は (NOT a = 1) AND b = 2 となります。

どちらの読み方もパースされ、異なるイベントを選択 します。そのため、注釈ではなくテストで保護されています。

NOT LIKE · NOT IN · NOT BETWEEN · IS NOT NULL は引き続きそれぞれ単一の演算子であり、単項 NOT がそれらの NOT を奪うことはありません。

負数​

負数は 符号付きリテラル であり、単項マイナスではありません。単項マイナスは * と二項 - への優先順位を定める必要がありますが、エンジンごとに回答が異なり、誤るとパース可否ではなく -a * b の 意味 が変わってしまいます。

減算はそのままです — a - 1 は減算です。


CEP 拡張

WINDOW TUMBLING — 壁時計に整列されたバケット​

SELECT id, count(*) AS c
FROM Point
WHERE quality = 0
WINDOW TUMBLING (1 min) ALIGN TO CALENDAR AT TIME ZONE 'Asia/Seoul'
GROUP BY id
SELECT count(*) AS c FROM Point WINDOW TUMBLING (30 sec) ALIGN TO FIRST EVENT
整列意味
ALIGN TO CALENDARカレンダー格子 に合わせます。タイムゾーンを併記できます
ALIGN TO FIRST EVENT最初のイベントを格子の開始点とします

タイムゾーンは 文章の記述 → PP_TZ → 拒否 の順で解決され、登録応答に 実際に使用されたタイムゾーンとその出所 が共に表示されます。

DST(夏時間)切り替えがバケットサイズの倍数でない場合は拒否されます

切り替え幅がバケットを分割できない場合、格子がずれます。静かにずれた格子は すべてのカウントを巧妙に誤らせる 原因となります。

WINDOW SLIDING — 直近 «サイズ», イベントごとに​

SELECT site_id, avg(latency) AS avg_latency
FROM Point
WINDOW SLIDING (5 min)
GROUP BY site_id

イベント時刻基準の直近区間を参照し、イベントが発生するたびに 再計算します。

ビューチェーン — ウィンドウの旧表記​

SELECT id FROM Point(type = 'int').std:groupwin(id).win:time_batch(1 sec) GROUP BY id
ビュー意味
win:time(<interval>)直近 n 時間区間 (スライディング)
win:length(<n>)直近 n 件 (スライディング)
win:time_batch(<interval>)n 時間ごとに区切られる バッチ
win:length_batch(<n>)n 件が溜まったら閉じる バッチ
win:ext_timed_batch(<시간필드>, <interval>)イベント 内のフィールド を時刻として使用
std:groupwin(<key>)キーごとに独立したウィンドウ
std:unique(<key>)キーごとに最新の1件のみ
std:lastevent() · std:firstevent()最後のイベント · 最初のイベント

フィルタはソースに属し、ビューチェーンはその後に付随します。 チェーンは集合ではなく 順序 です。

std:groupwin と GROUP BY は同じキーである必要があります

不一致がある場合、ウィンドウが一つの側に分かれ、集計が別の側にまとめられます。これにより、誰も記述していない文章に対して «正解» の回答が出ることになります。そのため、静かに一方を選択せず、拒否します。

OUTPUT … EVERY — 出力比率​

SELECT current_timestamp() AS timestamp, opc_id,
COALESCE(MIN(latency), 0) AS min_latency,
COALESCE(MAX(latency), 0) AS max_latency,
COALESCE(AVG(latency), 0) AS avg_latency
FROM Point.std:groupwin(opc_id).win:time_batch(10 sec)
GROUP BY opc_id
OUTPUT LAST EVERY 60 SEC
SELECT line_id, AVG(oee) AS avg_oee, MIN(oee) AS worst_oee
FROM OEE.win:time_batch(1 min)
GROUP BY line_id
OUTPUT LAST EVERY 1 MIN

ポリシーは LAST · FIRST · ALL · SNAPSHOT の4種類です。ティック間に生成された行は 次のティックまで保持 されます。これは装飾ではなく 比率制限 です。

INSERT INTO — 派生ストリーム​

INSERT INTO PointTimed
SELECT tag_id, AVG(latency) AS avg_latency
FROM Point.win:time_batch(1 min)
GROUP BY tag_id

結果行を名前付きストリームとして出力します。


行パターン

MATCH_RECOGNIZE — SQL:2016 標準​

SELECT tag, n FROM Point MATCH_RECOGNIZE (
PARTITION BY tag_id
MEASURES first(h.tag_id) AS tag, count(h.latency) AS n
AFTER MATCH SKIP TO NEXT ROW
PATTERN (h{3})
DEFINE h AS h.latency > 100
)
位置内容
PARTITION BYキーごとに個別にマッチング
MEASURESマッチから抽出する値
PATTERN行パターン — 列挙(連接) · |(交代) · 量化子 * + ? {n} {n,m}
DEFINE各変数の条件
AFTER MATCH SKIP次のマッチをどこから探すか

量化子の末尾にある ? は reluctant(可能な限り短く) です。

pattern[...] — 旧表記​

SELECT a.machineId AS machine, b.severity AS sev
FROM pattern [
every a=AlarmEvent(severity >= 3)
-> b=AlarmEvent(machineId = a.machineId, severity >= 4)
where timer:within(10 sec)
]

ラダーは緩い順に -> · or · and · until · every/not · ガード · アトムとなります。

表面が2つあるのは意図的であり、代償があります

MATCH_RECOGNIZE は標準であり、pattern[...] は既存の文章で使用されている表記です。同一の概念を2つの表記で記述できるという意味であり、どちらを使用するかはバックエンドが何を受け付けるかに依存します — バックエンド の表を参照してください。

不在 — 「来なかった」​

SELECT a.machineId AS machine
FROM pattern [every a=AlarmEvent -> not ClearEvent for 30 min]
SELECT 'gap' AS gap
FROM pattern [not AssetData(assetId = 'A-1') for 30 min]
期間は演算子の一部です — 除外できません

他のパターン演算子はすべて «到着» で終わりますが、不在には到着がありません。 そのため、「来なかった」ことをいつ結論づけてよいかを 文章で指定 する必要があります。指定がない場合、マッチングが終わらないか、誰も意図しない瞬間に終了します。

期間のない not は、文法が 読み込みを続けます。そして pattern:not:unbounded として名称を提示し、拒否します。文法から削除すると「タイポがある」ことになりますが、パーサーはその文章を正しく読み込んでいます。

単語が for である理由

WITHIN はこの言語に既に where timer:within(t) として存在し、極性が反対 です。マッチングが «勝ち抜けるべき» 期限に対し、非到着は «耐え抜くべき» 持続時間です。一つの単語に統合すると2つの文章が同じに見えますが、異なるイベント集合に発火 し、その差はランタイムで表面化しません。


関数

言語が認識する関数は 18個 のみです。ここにない名称は function:<name> として拒否されます。

関数分類ルール
count(*) · count(x)集計count(x) はNULLを数えません。絶対NULLではありません(0)
sum avg min max集計NULLを無視し、すべてNULLの場合はNULL
first(x) · last(x)集計ウィンドウ内 最初 · 最後 の 非NULL 値
stddev(x)集計標本 標準偏差(n−1)。非数値引数は拒否
median(x)集計中央値。非数値引数は拒否
coalesce(a, b, …)スカラー最初の非NULL。型は 拡張のみ — 数値・非数値の混在は拒否
cast(x, <type>)スカラーカンマ形式のみ。NULL → NULL
to_int to_long to_float to_doubleスカラー文字列 → 数値
concat(a, b, …) · a || bスカラー可変引数。一つでもNULLがあればNULL
current_timestamp()時間処理時刻 (epoch ms)
prev(n, x) · prior(n, x)ウィンドウ位置ウィンドウ内 n 番目の前のイベント
集計のNULLルール要約

集計はNULLを無視し、残ったものがなければNULLとなります。 例外は count のみで、それだけが 0 となります。


範囲外

実装しないと決定したもの​

対象理由
他のCEP製品の言語を完全に実装互換プロジェクトではないため
テンプレート化 (テンプレート + パラメータ)ロジックに種類を強制すると後々問題になるため
文章ごとのバックエンドルーティング1回の起動につき1つ。ルーティングが 二重発行欠陥の根源 であったため
バックエンドフォールバックフォールバックは 「どのエンジンがこの結果を出したか」 を曖昧にするため
CREATE STREAM 類 DDLイベントタイプの正本は API登録 である。文章内DDLを許容すると正本が2つになるため
文章ごとの EVENT TIME <열>イベント時刻は タイプ登録 が決定する。文章ごとに時計が異なると、同一タイプの2つの文章を比較できないため
UPDATE · DELETE · RETRACTEQLストリームは append-only であるため
ALERT · EMIT 句アラームは «シンクの名前» であり、言語の句ではない。文章ごとにトピックが生成され、アラームはそのトピックを購読する側で決定するため

未実装のもの​

構成現状
CONTEXTUNPARSEABLE
Named Window · Variable · サブクエリUNPARSEABLE
every-distinctUNPARSEABLE
percentile · to_bigdecimalUNSUPPORTED / function:<name>
WATERMARK · 許容遅延なし — 遅延イベントは 開いているバケットに合算 される
WINDOW SLIDING (크기, 슬라이드) (HOP)構文エラーとしてブロック済み
SESSION ウィンドウ移植集合外

🔴 「実装しない」と「未実装」を同じ欄に含めないでください。 前者は代替案を探すべき事項であり、後者はリクエスト可能な事項です。

関連ドキュメント​