omni-query について
Omni CLIを通じてOmniのセマンティックレイヤーでクエリを実行するスキルです。分析ユーザーがフィールド、ディメンション、メジャー、フィルターを指定すると、OmniがSQLに変換します。
主な機能
- 01セマンティックフィールドからのクエリ作成
- 02ディメンション、メジャー、フィルターの指定
- 03並べ替え、ピボット、行数制限
- 04テーブル計算の実行
- 05クエリ結果の処理
活用シーン
- 01トピック別の指標集計
- 02クエリ結果の絞り込みと並べ替え
- 03累計や変化率の計算
- 04SQLクエリの意図の再現
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ exploreomni/ omni-agent-skills --skill omni-queryインストール後、Claude Code で/omni-queryを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
evals/evals.jsonreferences/filter-expressions.mdjob-result-to-presentation.mdtable-calculations.md詳細
Omni クエリ
Omni CLI を使って、Omni のセマンティックレイヤーに対してクエリを実行します。Omni はフィールドの選択内容を最適化された SQL に変換するため、取得方法ではなく、取得したいもの(ディメンション、メジャー、フィルター)を指定します。
ヒント:利用可能なトピックやフィールドがわからない場合は、まず
omni-model-explorerを使ってください。
前提条件
# Verify the Omni CLI is installed — if not, ask the user to install it
# See: https://github.com/exploreomni/cli#readme
command -v omni >/dev/null || echo "ERROR: Omni CLI is not installed."
# Show available profiles and select the appropriate one
omni config show
# If multiple profiles exist, ask the user which to use, then switch:
omni config use <profile-name>
# Confirm the active profile is authenticated and inspect your permissions:
omni whoami whoami
認証:プロファイルは API key または OAuth で認証します。
whoami(または任意の呼び出し)が 401 を返す場合は、ユーザーに引き継いでください。ユーザーに! omni config login <profile>を実行するよう依頼します(OAuth 2.1 のブラウザーフロー。ブラウザーで約 2 分間処理がブロックされます)。ヘッドレス/CI セッションでは、自分でconfig loginを実行しないでください(ブラウザーがないためタイムアウトします)。ローカルの対話型マシンでは実行してもかまいません。プロファイルの設定方法(omni config init --auth oauth)と、--schemaによるコマンドやリクエスト本文の形式の確認方法については、omni-api-conventionsルールを参照してください。
モデル ID と、利用可能な トピックとフィールド に関する知識も必要です。
コマンドの確認
omni query --help # List query operations
omni query run --help # Show flags for running a query
omni query run --schema # Body schema + example (CLI ≥ 1.5.0 types the `query` object — `fields` and `modelId` are required, `calculations`/`filters`/`sorts`/`pivots` are enumerated; keys outside the typed set are still accepted but not shown)
omni query run --schema --field query # Just the typed query sub-fields
omni ai --help # AI-powered query generation
ヒント:プログラムで解析するために構造化出力を強制する場合は
-o jsonを、読みやすいテーブルを表示する場合は-o humanを使ってください。デフォルトはautoです(TTY では human、パイプに出力する場合は JSON)。
既知の問題と安全なデフォルト
- SQL を渡されたら、デフォルトではその意図をトピックで再現します — SQL が何をしているか(粒度、指標、フィルター、結合)を読み取り、トピックで表現できるか確認します(
omni-model-explorer、omni ai pick-topic/omni ai generate-query --run-query=false)。userEditedSQLを使うのは、適合するトピックがない場合、またはユーザーが SQL をそのまま実行するよう明示的に求めた場合に限ります。Raw SQL の実行を参照してください。反射的に SQL をそのまま渡さないでください(あなたはテキストから SQL を生成するツールではありません)。また、合わないトピックに無理に当てはめないでください。 - ユーザーが計算列、テーブル計算、累計、移動平均、変化率、行合計、階層ラベル、VLOOKUP、SUMIF、または日付の差を求めた場合は、
query.fieldsで選択したcalculations[]のテーブル計算で対応します。ユーザーが代替手段を明示的に求めない限り、既存のモデルフィールド、userEditedSQL、クライアント側の計算、説明文だけでの回答に置き換えないでください。 - テーブル計算の回答は監査可能にしてください。 最終回答で計算列の名前を示し、同じ
calc_nameがquery.fieldsとcalculations[]の両方にあることを示すまでは、タスクを完了扱いにしないでください(OMNI_RUNNING_TOTALのような単純な演算子でも同様です)。簡潔なクエリ JSON の抜粋を含めてください。query.fields、演算子とオペランドをそのまま保持した実際のcalculations[]オブジェクト、関連するpivots[]/limit、検証結果を示します。{ "OMNI_OFFSET_MULTI over": "field" }のような言い換えや、「OMNI_RUNNING_TOTALで計算」といった説明では不十分です。長い CSV でクエリの構造が埋もれる場合は、数行と再利用可能な構造を示すか、簡潔にするため完全な JSON を省略する旨を記載してください。 - 計算クエリが成功しても計算列が空欄なら、原因が判明するまでは計算失敗として扱ってください。オペランドの順序、
for_calc、日付の切り捨て、outside_pivot、およびcalc_nameがquery.fieldsに含まれているかを再確認してください。 - 作成中または検証中に計算エラーを握りつぶさないでください。
swallow_errors: false(デフォルト)を維持し、不正な計算では実際のエラーメッセージが明確に表示されるようにします。swallow_errors: trueにすると、列には#ERROR!がひっそり表示される一方、クエリはCOMPLETEを返すため、データ、空欄の計算列、エンジンのバグと誤解しやすくなります。#ERROR!が表示された場合は、swallow_errors: falseで再実行して原因を明らかにしてください(多くの場合、参照先フィールドがquery.fieldsにありません)。ドキュメント、ダッシュボードタイル、またはomni aiジョブから取得した計算クエリを再実行する場合は、一字一句そのまま実行してください。計算が参照しているフィールドを削除すると、計算自体に原因のないエラーが発生します。swallow_errors: trueは、セルごとの回復性が必要な完成済みタイルに限って使い、その前にfalseで検証してください。references/table-calculations.md§5.11 と §6.5 を参照してください。 - テンプレートがある場合は、低レベルの生 SQL /ウィンドウ AST より、ドキュメント化された Omni の計算演算子を優先してください。たとえば、プロンプトがテーブル計算を求めている場合は、手書きの
window_call/LAGではなく、移動平均にOmni.OMNI_RUNNING_TOTAL、Omni.OMNI_PERCENT_CHANGE_FROM_PREVIOUS、Omni.OMNI_FX_AVERAGE(Omni.OMNI_OFFSET_MULTI(...))を使います。 query runでは、毎回queryオブジェクトの内側にmodelIdが必要です。 単独のリクエスト本文は{"query":{"modelId":"<uuid>", …}}です。これを省略するとquery.modelId: Invalid input: expected string, received undefinedで 400 エラーになります。これは v2 のダッシュボードタイルクエリ(omni-content-builder)とは正反対です。タイルのクエリにはmodelIdを決して含めないでください(サーバーがドキュメントのワークブックモデルにタイルを紐付けます)。タイルのルールを単独クエリに適用しないでください。タイルではmodelIdを省略し、query runでは必須です。 ブランチまたはワークブック/ドラフトモデルに対して実行する場合は、共有モデルではなく、そのモデルの ID(ブランチモデル ID またはドラフトのworkbookModelId)をmodelIdに設定してください。また、スキルで明示的に求められた場合に限り、トップレベルにbranchIdを渡します(ブランチ固有のモデル ID を指定すれば、そのブランチのフィールドがすでに解決されます)。- 結果の行を読み取るには、本文の最上位に
resultType:"json"(または"csv")を設定してください。queryの内側には入れません。queryの内側に置くと黙って無視され、デフォルトの base64-Arrow ストリーミング形式の応答が返されます(行を読めず、余計な往復が発生します)。結果の処理と検証を参照してください。
トピックを基にクエリを構築する
裸のベースビューではなく、すべてのクエリをトピックを基に構築することをおすすめします。トピックには、管理された結合、ラベル、アクセス権が含まれます。また、トピックを基に構築されていないクエリは、アクセスが制限されたクエリ実行者/閲覧者には利用できません(モデラー/管理者としては機能しますが、アクセスが制限されたロールではエラーなく失敗します)。クエリのtableをトピックのベースビューに設定し、join_paths_from_topic_name: <topic>を渡します。また、あなたが制限付きクエリ実行者(QUERY_TOPICSはあるが、QUERY_FULL_MODELはない — whoamiで確認)である場合、トピックベースは単なる推奨ではなく、唯一の選択肢です。裸のベースビューを使ったクエリや、raw SQLのuserEditedSQLクエリにはQUERY_FULL_MODELが必要です。そのため、あなたが作成してコンテンツに配置するすべてのクエリをトピックベースにする必要があります。
結合マップによる結合ビューのフィールドの解決方法。 tableはトピックのベースビューのままにします。join_paths_from_topic_nameを使うと、トピックの結合マップを経由して、ベースビューから結合ビューのフィールドに到達できます。たとえば、order_itemsベースのトピックでusers.stateを選択する場合、tableはorder_itemsのままで、結合はトピックから行います。table: usersには設定しません。join_paths_from_topic_nameを省略する(またはtableをベースビュー以外のビューに設定する)と、結合ビューのフィールドが解決できなかったり、誤った結合が行われたりする可能性があります。omni models get-topic <modelId> <topic>を実行して、ベースビューと、到達可能なすべての結合を確認してください。base_view_nameとjoin_via_mapに、ベースビューと各到達可能なビューへの結合パスが表示されます。(これがトピッククエリの標準的な形です。omni-content-builderのタイルやomni-model-builderの検証クエリでも同じ形を使います。)
クエリの取得元を決める:
- 既存のトピックで対応できる(そのベースビュー+結合で到達可能なビュー)→ そのトピックに対してクエリを実行します。
- フィールドは結合で到達可能なビューにあるが、トピックから公開されていない/結合がない → 先にトピックの拡張(リレーションシップ/結合の追加)を提案し、その後
omni-model-builderで構築します。 - 対象、制約、または対象者が根本的に異なる → 新しいトピックの作成を提案します(
omni-model-builderの「新しいトピックと拡張のどちらを選ぶか」の基準を参照)。まず依頼者に確認し、ブランチ上で構築してください。
フォールバック — トピックを使わないクエリ経路。 トピックの外部で実行する経路が2つあります。裸のベースビュー(結合にはtable:とグローバルなrelationshipsファイルを使用)と、raw SQL(userEditedSQL。「Raw SQLの実行」を参照)です。どちらにも同じ注意点があります。トピックのスコープで適用される制御、つまりアクセスフィルター(行レベル)とalways_whereは適用されません。また、ダッシュボードでは、デフォルトでViewer/Restricted Querierロールにタイルが表示されません(アクセスが制限された対象者への対応はコンテンツ権限の問題です。**omni-content-builder**を参照)。2つの経路は、オブジェクトレベルのアクセス許可の扱いが異なります。裸のビューを使ったクエリでは引き続き適用されますが、raw SQLではそれも回避されます(最も許可範囲の広い経路です)。クエリを表現できるトピックがある場合はトピックを優先し、ほかの方法ではクエリを表現できない場合に限り、トピックを使わない経路を選んでください。
結論が「トピックを構築または変更する」であれば、適切に対応するため**omni-model-builder**に引き継いでください。
複合トピック。 複合トピック(list-topicsのis_composite: true)にはベースビューがありません。join_paths_from_topic_name: <composite>を渡し、tableは指定しません。フィールドは@_shared_dimensions_.<name>[timeframe]、@_shared_views_.<view>.<field>、@<topic>.<view>.<measure>の形式で指定します。@<topic>.をキーとするフィルターは、そのメンバートピックのみに適用されます。@は必須です。メンバートピックの1つに属するディメンションはフィルターに使えますが、複合トピックのデフォルトのunrelated_dimension_handlingでは選択できません。グループ化に使う必要がある場合は共有ディメンションにするか、複合トピックでnull_fill(他のメンバーのメジャーがnull行に配置されます)またはrepeat(すべての行で繰り返されます)が宣言されることを想定してください。複合トピックに対するget-topicで、アドレス指定できる項目が一覧表示されます。
クエリの実行
基本的なクエリ
omni query run --body '{
"query": {
"modelId": "your-model-id",
"table": "order_items",
"fields": [
"order_items.created_at[month]",
"order_items.total_revenue"
],
"limit": 100,
"join_paths_from_topic_name": "order_items"
}
}'
クエリパラメーター
| パラメーター | 必須 | 説明 |
|---|---|---|
modelId | はい — query 内 | Omni モデル(またはブランチ/ワークブックモデル ID)の UUID です。単独の query run では毎回必須です。省略すると 400 エラーになります。対照的に、v2 ダッシュボードのタイルクエリでは完全に省略します。 |
table | 条件付き | ベースビュー(FROM)です。join_paths_from_topic_name が設定されている(ベースビューはトピックから取得)場合、または userEditedSQL が使われている(テーブルは無視)場合を除き、セマンティッククエリで必須です。 |
fields | はい | view.field_name 参照の配列 |
join_paths_from_topic_name | 推奨 | 結合解決に使用するトピック |
limit | いいえ | 行数制限(デフォルトは 1000、最大は 50000、無制限にする場合は null)。トピックまたはモデルの default_row_limit はここでは適用されません。limit を省略すると常に 1000 になります |
sorts | いいえ | ソートオブジェクトの配列 |
filters | いいえ | フィルターオブジェクト |
pivots | いいえ | ピボットするフィールド名の配列 |
フィールドの命名
フィールドには view_name.field_name を使用します。日付フィールドでは、期間を角括弧で指定できます。
users.created_at[date] — Daily
users.created_at[week] — Weekly
users.created_at[month] — Monthly
users.created_at[quarter] — Quarterly
users.created_at[year] — Yearly
ソート
"sorts": [
{ "column_name": "order_items.total_revenue", "sort_descending": true }
]
フィルター
query.filters は、fieldName → 型付きフィルターオブジェクトのマップです。Omni の UI が出力する形式です(稼働中のダッシュボードの filterConfig と query run によるエンドツーエンドの検証済み)。
"filters": {
"users.state": { "type": "string", "kind": "EQUALS", "values": ["California", "New York"] },
"products.category": { "type": "string", "kind": "CONTAINS", "values": ["Jeans"] },
"order_items.sale_price": { "type": "number", "kind": "GREATER_THAN", "values": [100] },
"order_items.created_at": { "type": "date", "kind": "BEFORE", "values": ["2024-01-01"] },
"order_items.shipped_at": { "type": "date", "kind": "TIME_FOR_INTERVAL_DURATION", "ui_type": "PAST", "left_side": "12 months ago", "right_side": "12 months" },
"order_items.is_shipped": { "type": "boolean", "is_negative": false }
}
type—string/number/date/boolean。ブール値フィルターではis_negativeを使用します(false= true、true= false)。kind/values(および任意のtreat_nulls_as_false)は使用しません。kind(演算子。string/number/date 用)— string:EQUALS、CONTAINS、STARTS_WITH、ENDS_WITH、IS_EMPTY、SQL_LIKE。number:EQUALS、GREATER_THAN、LESS_THAN、BETWEEN。date:BEFORE、ON_OR_AFTER、BETWEEN、TIME_FOR_INTERVAL_DURATION/TIME_FOR_UNIT_DURATION(ローリングウィンドウ)、IS_ON_DAY_OF_WEEK、…(QUERY_OFFSETは別のクエリを参照します)。values— オペランドの配列です。単一の値、リスト(複数選択のEQUALS)、またはBETWEENの[lo, hi]を指定します。日付のローリングウィンドウでは、valuesの代わりにleft_side/right_sideとui_type(例:PAST)を使用します。- フィルターが実際に適用されていることを確認してください。 型に合わないプロパティを持つフィルターオブジェクト(たとえば、
is_negativeが必要なbooleanにkind/valuesを指定した場合)は黙って無視されます。クエリはCOMPLETEを返しますが、フィルターは SQL に反映されません。cache:"SkipCache"を指定してsummary.display_sql(WHERE)を確認するか、行数が実際に変化することを確認してください。kind/ui_type/ブール値の正確な列挙値はomni documents v2-create --schemaのフィルターオブジェクト内にあります。references/filter-expressions.md も参照してください。
文字列だけの簡略形式は使用しないでください(
"order_items.status": "complete"、"last 90 days"、"not null")。クエリ API はこれを拒否します。2026 年 8 月時点では400 "Unable to parse data stream"、古いビルドでは500 "Cannot use 'in' operator to search for 'query_id' in <value>"が返されます。上記の型付きオブジェクトが確実に動作する形式です。(現在の Omni で、値、日付、null 文字列、数値、ブール値の各形式を再現して確認済みです。)
ピボット
{
"query": {
"fields": ["order_items.created_at[month]", "order_items.status", "order_items.count"],
"pivots": ["order_items.status"],
"join_paths_from_topic_name": "order_items"
}
}
ピボットクエリでは、リクエスト本文のトップレベルに resultType を設定しない限り、limit: null は拒否されます。 明示的な数値の上限(例:5000)を指定してください。
メジャーを行に転置する(transposed_measures)
1 つの横長の行にある複数のメジャーを縦長形式(メジャーごとに 1 行)に変換し、メジャーをグラフの分類軸として使えるようにします(例:ファネルや、「軸にメジャーを配置する」棒グラフ)。transposed_measures は、変換するメジャーフィールド名の配列(fields に指定する名前と同じ)であり、ブール値ではありません。true を渡すと、エラーなしで拒否され(z.array(z.string()))、結果は空になります。
{
"query": {
"table": "order_items",
"fields": ["order_items.units_sold", "order_items.shipped_items", "order_items.delivered_items"],
"transposed_measures": ["order_items.units_sold", "order_items.shipped_items", "order_items.delivered_items"],
"join_paths_from_topic_name": "order_items"
}
}
結果には、最初に転置されるメジャーの位置に、次の 3 つの合成列が追加されます。
measure_name— メジャーのフィールド名です。ビジュアライゼーションでは、メジャーの表示名(例:「販売単位数」)として表示されます。measure_order— リストの順序(ステージの順序)に基づく0, 1, 2, …です。measure_value— その行におけるメジャーの値です。
複数のメジャーからファネルを作成するには、この方法がサポートされています(Omni のファネルには、N 個のメジャーではなく、ステージのディメンションと 1 つのメジャーが必要です)。measure_name をステージ、measure_value を値としてグラフにします。omni-content-builder → 設定オブジェクト:ファネル を参照してください。
テーブル計算
クエリ実行後に算出する列(累計、全体に対する割合、比率、条件分岐など)です。calculations[] に AST オブジェクトとして記述します。クエリ API には解析済み AST が必要です。ワークブックのフロントエンドで使われる {name, formula} 形式は受け付けません。
最小限の計算式 — calculations[] にエントリーを1つ指定します(その calc_name も query.fields に含める必要があります)。
{ "calc_name": "calc_pct", "label": "% of Total", "format": "0.0%",
"sql_expression": { "type": "call", "operator": "Omni.OMNI_PERCENT_OF_TOTAL",
"operands": [{ "type": "field", "field_name": "orders.total_revenue", "for_calc": true }] } }
最大の落とし穴: calc_name は query.fields にも(ダッシュボードタイルでは外側の queryPresentation.fields にも)指定する必要があります。calculations[] で定義しても fields にない計算式は、計算されますが表示されません。
5つのクイックテンプレート演算子(いずれも for_calc: true を指定した field オペランドを1つ取ります):
Omni.OMNI_PERCENT_OF_TOTAL、Omni.OMNI_PERCENT_OF_PREVIOUS、Omni.OMNI_PERCENT_CHANGE_FROM_PREVIOUS、Omni.OMNI_RUNNING_TOTAL、Omni.OMNI_RANK。
計算式の形がすでにわかっている場合は、手動で作成するかコピーした AST を使って omni query run を実行してください。テーブル計算、期間比較、複数ステップの分析など、複雑なものを 生成する 場合は、エージェント型の方法(omni ai job-submit)を優先してください。generate-query では暗黙に破棄される計算式(前月比など)も、こちらなら作成できます。エージェント型ジョブから 再利用可能な AST を取得するには、ジョブの actions[].generate_query の結果から構造化クエリ/calculations を取り出します(resultSummary や userEditedSQL/SQL フォールバックからは取得しないでください)。その後、組み立てたクエリを swallow_errors:false で再実行し、値をジョブの csvResult と比較してください。ジョブでは計算式をすでに実行済みなので、再実行は計算自体を再検証するためではなく、組み替えを確認するためのものです(フィールドの削除や名前変更は、「実行できて行が返された」という確認では見逃される翻訳上の失敗です。詳細は table-calculations.md §6 を参照してください)。generate-query は、単純で決定的な単一クエリと、クエリの実行が許可されていない場合の形だけの下書きに限定してください。
ユーザーがモデルの変更を明示的に求めない限り、クエリタスクは読み取り専用です。フィールドが見つからない場合は、トピック/ダッシュボードのクエリを調べ、適切なモデル/トピック/ブランチを使うか、フィールド不足のブロッカーとして報告してください。クエリを動かすためだけにブランチを作成したり、メジャーを追加したり、YAML を編集したりしないでください。(また、クライアント側で計算したり、users.tier_label などの既存のモデルフィールドを使ったりして計算式の要求に応えないでください。テーブル計算式を作成して検証してください。「既知の問題」を参照。)
よくある要望 → 演算子(正確な AST とレシピごとの注意点はリファレンスを参照し、その場しのぎで作成しないでください):
- 全体に対する割合 →
OMNI_PERCENT_OF_TOTAL· 累計 →OMNI_RUNNING_TOTAL(時間順に昇順でソートし、Omni の外側で逆順にしないでください) · 前月比 →OMNI_PERCENT_CHANGE_FROM_PREVIOUS(omni_period_pivot/LAGではありません) · 直近N件の平均 →OMNI_FX_AVERAGEとOMNI_OFFSET_MULTI - ピボット行合計 →
OMNI_FX_SUMとOMNI_PIVOT_OFFSET(outside_pivot:true、数値のlimit) · ティアラベル →OMNI_FX_IFS(CASE/モデルフィールドではありません) · SUMIF →OMNI_FX_SUM_IF· VLOOKUP →OMNI_FX_VLOOKUP(文字列検索でNo referenced query…の 400 エラーが発生する場合は、OMNI_FX_SUM_IFに切り替えてください) · 日付差分 →OMNI_FX_DATEDIF([date]オペランド)
各レシピの正確な JSON AST、演算子の全カタログ(Omni.*/SqlStdOperatorTable.*)、ノードタイプ、検証ルール、見慣れない計算式を往復させる方法については、references/table-calculations.md を参照してください。SKILL.md はワークフローのガードレールであり、詳細な形式はリファレンスに記載されています。
実行時、計算式はベース集計をラップする外側の SELECT にコンパイルされます。ウィンドウ形式の演算子はそこで ... OVER (...) を出力するため、共有データモデルでウィンドウ関数をサポートする必要はありません。ピボットしたクエリでは、テンプレート演算子はセグメントごとの系列を計算するためにピボット列で自動的にパーティション分割されます。ピボット列をまたいで行を集計するには、outside_pivot: true を設定し、OMNI_PIVOT_OFFSET を集計関数でラップしてください。
生 SQL の実行(userEditedSQL)
SQL が指定されていますか? まずトピックを通して再現してください(既知の問題を参照)。セマンティックレイヤーを通じて SQL の意図を表現し、トピックで表現できない場合(SQL 優先の移行、ウェアハウス固有の SQL、単発のアドホックな読み取り)、またはユーザーがそのまま実行するよう求めた場合に限り、生の
userEditedSQLを使用してください。忠実に再現するには存在しないものの、本来あるべきフィールドやトピックが必要になる場合は、生 SQL に頼るのではなく、そのモデル化(omni-model-builder)を提案してください。
generate-queryの出力の読み方: トピックにメトリクスに必要なメジャーがない場合、generate-queryは${}テンプレートを含むuserEditedSQL(例:SUM(${view.sale_price}) AS sale_price_sum FROM ${Topic})を返し、SQL 出力のエイリアス(sale_price_sumなど)をfieldsに列挙します。これらのエイリアスは モデルフィールドではありません。SQL を取り除いてセマンティッククエリとして実行しようとしないでください(解決されません)。また、${Topic}トークンが解決されるのはgenerate-query自身の実行中のみです。このテンプレートクエリはquery runから直接実行したり、ダッシュボードのタイルとして保存したりすることはできません(No such view "Order Items"エラーになります)。そのまま再利用せず、トピックにメジャーが不足しているサインとして捉え、そのメジャーを追加し(omni-model-builder)、メトリクスを適切なセマンティックフィールドにしてください。
userEditedSQL は トピックを使わないクエリ経路であり、ビュー単体のクエリと同じ系統です(「フォールバック — トピックを使わないクエリ経路」を参照)。これはセマンティックレイヤーで表現できない SQL のためのエスケープハッチです。適合する場合は、トピックまたはセマンティックフィールドを優先してください。
omni query run --body '{
"query": {
"modelId": "<model-id>",
"fields": [],
"userEditedSQL": "select count(*) as cnt from ECOMM.ORDER_ITEMS"
}
}'
fieldsは必須です(配列。空の[]でも可)。tableは不要です。SQL が正となるため、userEditedSQLが設定されている場合、値の入ったfields/tableは無視されます。- モデルの 接続に対して実行されます。フィールド参照ではなく、完全修飾名を使ってウェアハウスの方言で SQL を記述してください。
"rewriteSql": falseを指定すると、SQL がそのまま実行されます(デフォルトでは解析後に再出力され、識別子の引用符が付け直され、射影がview.field名前空間にエイリアス付けされます)。"dbtMode": trueを指定すると、Jinja/dbt のテンプレートを使用できます。(どちらもキャメルケースです。queryオブジェクトは許容的なため、キーのスペルミスやスネークケースは黙って破棄されます。)- 権限による制限があります: クエリ実行者のロールには、手書き SQL の実行を許可する権限が必要です。権限がない場合、ジョブは
error_type: "FORBIDDEN"、「queries based on manually written SQL are restricted」 という内容で失敗し、4xx ではなく、ジョブ本文内のエラーとともに HTTP 200 が返されます。 - アクセス時の動作: 生 SQL は、モデルの制御(オブジェクトレベルの アクセス許可、行レベルの アクセスフィルター、always_where)をすべて迂回し、ダッシュボードの Viewer/Restricted Querier ロールからは見えません。最も制限の緩い経路です。権限のあるユーザーによるアドホックな読み取りにのみ使用し、再利用するクエリやダッシュボードのクエリからは必ず取り除いてください(「ダッシュボードでジョブ結果を使用する」を参照)。
- 行数上限: 上限のない生クエリは 50,000 行で打ち切られます(レスポンスには 50,001 行が返され、最後の 1 行は上限到達を示すセンチネルです)。エンベロープの
limitは生 SQL に適用されません。結果を制限するには、SQL 自体にLIMITを指定してください。
リクエストレベルのオプション(query の外部)
これらのキーは、query の内部ではなく、query と並ぶ本文の最上位に置きます。query オブジェクトは寛容なため、誤って query 内に置いたキー(例:resultType、cache)は拒否されず、黙って無視されます。そのため、オプションが無視されたまま呼び出しは「成功」します。例外は branchId で、query 内にネストすると拒否されます。resultType の配置ミスを示す兆候は、JSON/CSV を指定したのに、base64 の Arrow エンベロープが返ってくることです。
| オプション | 説明 |
|---|---|
resultType | 出力形式:csv、xlsx、または json です。最上位のみ。query 内に置くと黙って無視され、Arrow が返されます。デフォルトの base64 Arrow レスポンスを使う場合は省略します。 |
cache | キャッシュポリシー:Standard(デフォルト)、SkipRequery、SkipCache、SkipCacheAndRebuildExtracts(クエリが読み取る抽出データも再構築します)。 |
userId | 別のユーザーとして実行します(組織スコープの API キー)。--user-id フラグも使用できます。 |
branchId | モデルのブランチを対象に実行します(ドラフトモデルの変更を本番データで検証します)。同じ共有モデルのブランチである必要があります。 |
planOnly | クエリを実行せずに実行計画を返します(ウェアハウスのコストをかけずに検証/デバッグできます)。resultType とは併用できません。 |
formatResults | エクスポート時に、生の値ではなく書式設定済みの値(例:$1,234.56)を出力します。resultType が必要です。Arrow では無視されます。 |
timezone | リクエスト単位のタイムゾーン上書き(IANA ID)です。接続設定 allowsUserSpecificTimezones と組織設定 allowsDocumentCanUseTimezoneOverride の両方が必要です。いずれかがオフの場合、黙って何も行いません。 |
workbookUrl | クエリ用の一時的なワークブックも作成し、ユーザーに「Omni で開く」リンクを表示します。CLI ≥ 1.3.0 では手動で設定する代わりに --workbook を渡します。リンクは本文ではなくレスポンスのヘッダーで返されます。CLI は人間向け出力ではリンクを表示し、JSON モードでは {"workbookUrl": …} として stderr に出力します。(対象の)ユーザーがモデルのワークブック権限を持たない場合も、クエリは成功し、リンクは黙って省略されます。リンクがない場合は、再試行が必要な失敗ではなく、権限がないことを示します。 |
集計テーブルが使われたことの確認
モデルで集計テーブル(materialized_query)が宣言されている場合は、planOnly: true と cache: SkipCache を指定してクエリを計画し、summary.display_sql を確認します。集計テーブルから提供されたクエリには、-- Query rewritten to use materialized view "<view>" の見出しが付き、その下に元の SQL がコメントとして記載されます。見出しがなければ、ファクトテーブルが使われています。複合トピックでは各メンバーのクエリが個別に照合され、見出しには最初に使われたテーブルの名前が記載されます。そのため、ほかのメンバーの SQL も確認してください。クエリが集計テーブルに該当しない理由については、omni-model-builder の集計認識に関するリファレンスを参照してください。
結果をユーザーに表示する(CLI ≥ 1.3.0)
人間向けモードでは、query run はモデルのラベルと数値形式を使って書式設定済みのテーブルを表示し、未完了のジョブも自動でポーリングします。--chart を使うと、同じ結果をターミナル上の棒グラフ形式のテーブルとして描画します(--chart-value でメジャーを選択し、--chart-rows で行数の上限を設定できます)。パイプで出力する場合も幅 80 列で描画されるため、チャットのコードブロックに収まります。--chart は本文から resultType を取り除き、-o json との併用は拒否されます。どちらの表示形式にもジョブのエンベロープは含まれません。まず JSON モードで実行して検証し(下記参照)、その後ユーザー向けに表示してください。
結果の処理と検証
omni query runは NDJSON をストリーム出力します。単一の JSON オブジェクトではありません。 JSON モードでは、解析時に-o jsonを指定してください。config set-formatまたはOMNI_OUTPUT_FORMATによる人間向けのデフォルト設定は、パイプに出力する場合でも適用され、テーブルが出力されます。CLI は 複数の JSON オブジェクトを 1 行に 1 つずつ 出力します。まず{"jobs_submitted":{…}}の行が出力され、その後に 1 行以上の{"job_id":…,"status":"COMPLETE","summary":{…}}ジョブ行が続きます(resultTypeを指定した場合は、結果ペイロードも含まれます)。単純にjson.loads(entire_stdout)を実行すると、JSONDecodeError: Extra dataが発生します。単一オブジェクトのパーサーは作成しないでください。 行を反復処理して必要なものを選ぶか、jq -sでまとめて読み込むか、空でない最後の行を読み取ってください。--compactを指定すると、各オブジェクトが整った 1 行で出力されます。
デフォルトのレスポンスは、Base64 エンコードされた Apache Arrow テーブルです。Arrow の結果はバイナリであり、生のレスポンスから個々の行データを解析することはできません。行数は cache_metadata.num_rows にあります(summary.row_count ではありません)。summary オブジェクトには検証メタデータが含まれます:invalid_calculations、missing_fields、display_sql(コンパイル済み SQL)、omni_sql_parse_failed。--schema にはこれらは表示されません。これはリクエスト本文のみを表し、レスポンスの形式は実際のレスポンスから確認します。ルールについては、omni-api-conventions を参照してください。
自分で行を読み取るには、本文の TOP LEVEL に必ず resultType: "json"(または "csv")を設定してください。 すると標準出力に、Arrow/NDJSON のラッパーを展開する必要のない、直接解析可能な JSON 配列(または CSV)が出力されます。値をスポットチェックするうえで、これが唯一信頼できる方法です。デフォルトの Arrow パスを選び、そこから行をデコードしようとするのは、よくある時間の無駄です。
{ "query": { ... }, "resultType": "csv" }
resultType: "xlsx" も有効ですが、バイナリの .xlsx ファイル(ZIP ベース)が返されます。デフォルトの Arrow BLOB と同様、スプレッドシートアプリまたはライブラリなしではインラインで読み取れません。これは結果を調査するためではなく、人にファイルを渡す場合にのみ使用してください。エージェント側で読み取る場合は、csv/json を使用してください。
結果の検証
すべてのクエリレスポンスは、結果を信頼したりユーザーに提示したりする前に確認してください。
エラーを確認する:
- レスポンスに
errorキーが含まれている場合、クエリは失敗しています。よくある原因:フィールド名の誤り、結合パスの欠落、不正なフィルター式、権限エラー。 - レスポンスに
remaining_job_idsが含まれている場合、クエリはまだ実行中です。結果を確認する前に、omni query waitでポーリングしてください。
行数を確認する(フィールドは cache_metadata.num_rows):
cache_metadata.num_rows == 0— クエリからデータが返されませんでした。フィルター範囲内にデータがない場合など、妥当なこともありますが、ユーザーに伝える価値があります。よくある原因:フィルターが厳しすぎる、日付範囲が誤っている、該当する行がないフィールド。cache_metadata.num_rowsが設定したlimitと等しい場合、結果が切り詰められている可能性があります。ユーザーが完全なデータを必要としている場合は、より大きな上限を設定するか、上限なしを意味するnullを指定して再実行してください。
CSV でデータをスポットチェックする:
正確性が重要な場合は、CSV をリクエストし、出力を確認してください。
omni query run --body '{
"query": { ... },
"resultType": "csv"
}'
次の点を確認してください:
- 列ヘッダーがリクエストしたフィールドと一致している
- 値が想定範囲内にある(例:売上がマイナスではない、日付が未来ではない)
- 集計に不自然な点がない(例:件数が合計を返していない)
フィルターの動作を検証する:
クエリにフィルターが含まれている場合、フィルターが適用されていることを確認してください。
# Run the same query without filters
omni query run --body '{ "query": { ... (no filters) ... }, "resultType": "csv" }'
# Compare row counts — filtered should be <= unfiltered
両方のクエリで同じ行数が返された場合、フィルターが適用されていない可能性があります。フィールド名と、フィルターオブジェクトがフィールドの型に一致していることを確認してください(boolean には kind/values ではなく is_negative が必要です。一致しない場合は、警告なしに無視されます)。summary.display_sql に条件が表示されていることを確認してください(cache:"SkipCache")。
検証チェックリスト
| チェック項目 | 方法 | タイミング |
|---|---|---|
| 応答にエラーがない | error キーを確認します | すべてのクエリ |
| 計算/フィールドが有効 | summary.invalid_calculations と summary.missing_fields が空であることを確認します | すべてのクエリ(特に計算を含む場合) |
| データが返された | cache_metadata.num_rows > 0 を確認します | すべてのクエリ |
| 結果が切り捨てられていない | cache_metadata.num_rows < limit を確認します | 完全性が重要な場合 |
| 列が正しい | CSV の列ヘッダーが要求したフィールドと一致することを確認します | ダッシュボードまたはレポートの作成時 |
| 値が妥当 | CSV の出力を抜き取り確認します | ユーザーへの提示時 |
| フィルターが適用されている | フィルター適用前後の行数を比較します | フィルターの使用時 |
| 長時間実行クエリが完了している | 最終応答に remaining_job_ids がないことを確認します | 大きなテーブルへのクエリ時 |
Arrow の結果のデコード
import base64, pyarrow as pa
arrow_bytes = base64.b64decode(response["data"])
reader = pa.ipc.open_stream(arrow_bytes)
df = reader.read_all().to_pandas()
長時間実行クエリ
応答に remaining_job_ids が含まれている場合は、完了するまでポーリングします。
omni query wait --job-ids job-id-1,job-id-2
JSON モードではポーリングは行われません。ストリームは待機ウィンドウの終了時に終わり、結果が未完了であることを示すのはフッターの remaining_job_ids だけです(CLI ≥ 1.3.0 のヒューマンモード出力ではポーリングが行われます)。
大きな結果をチャンクでストリーミングする(CLI ≥ 1.5.0)
1 回の応答に収まらない大きな結果の場合、またはダウンロードの途中で接続が切れた場合は、--completion-only true で完了処理と結果の取得を分離し、後から omni query results で結果を取得します。その後、query run/query wait のジョブ行には result が含まれなくなり、Arrow IPC バイト列は base64 のスライスとして返されます。これらを連結し、stream_stats.next_offset が null になった時点で解析します。結果はジョブ完了後、およそ 1 時間は読み取り可能です。
# 1. Wait for completion without downloading the payload
omni query wait --job-ids "$JID" --completion-only true
# 2. Read the result in slices (resume on drop by passing the previous next_offset)
omni query results --job-id "$JID" --offset 0
omni query run では、同じ効果を得るために本文の最上位に "completionOnly": true を設定します。resultType とは併用できません。
ダッシュボードからクエリを実行する
既存のダッシュボードを支えるクエリを抽出して再実行します。
# Get all queries from a dashboard
omni documents get-queries <dashboardId>
# Run as a specific user
omni query run --body '{ "query": { ... }, "userId": "user-uuid-here" }'
# Cache policy (valid values: Standard, SkipRequery, SkipCache, SkipCacheAndRebuildExtracts)
omni query run --body '{ "query": { ... }, "cache": "SkipCache" }'
保存されたクエリを保存時の内容のまま実行する(CLI ≥ 1.5.0)。omni documents run-query <identifier> <queryId> は、ドキュメントに保存されたクエリの 1 つをサーバー側で実行し、単一の JSON/CSV/XLSX ドキュメントを返します。クエリ本文はサーバー上で読み込まれるため、呼び出し元が実行時に置換または追加できるのはフィルターのみです。ダッシュボード上のタイル(またはアプリを持つドキュメントの任意のクエリ)は、それを閲覧できるユーザーであれば誰でも実行できます。非表示のタイルやダッシュボード外のタブには、ワークブックへのアクセス権が必要です。<queryId> は、タイルの queryIdentifierMapKey、または omni documents get-queries が返すクエリのプレゼンテーション ID です。実行ごとの filters はフィールド名をキーにします。適用できないフィルターがある場合、フィルターなしで実行するのではなく、実行は 400 エラーで失敗します。組織スコープのキーを使う場合は、get-queries にも --user-id を指定すると、そのメンバーとして実行されます。保存時の動作を維持したい場合は、クエリ本文全体を再構築して query run を実行するより、この方法をおすすめします。
AI を活用したクエリ生成
クエリ JSON を手動で構築する代わりに、自然言語で希望を説明し、Omni の AI にクエリを生成させることができます。
クエリの生成(同期)
最も速い方法です。生成されたクエリ JSON を同期的に返します。クエリ構造のみを取得して実行しない場合は --run-query false を渡します(デフォルトではクエリを実行します)。
# Just generate the query JSON (no execution)
omni ai generate-query your-model-id "Show me revenue by month" --run-query false
応答:
{
"query": {
"fields": ["order_items.created_at[month]", "order_items.total_revenue"],
"table": "order_items",
"filters": {},
"sorts": [{"column_name": "order_items.created_at[month]", "sort_descending": false}],
"limit": 500
},
"topic": "order_items",
"error": null
}
# Generate and execute in one call
omni ai generate-query your-model-id "Top 10 customers by lifetime spend"
任意のフラグ:
--branch-id— 特定のモデルブランチに対してテストします--current-topic-name— トピックの選択を特定のトピックに限定します
トピックの選択
完全なクエリを生成せずに、質問に対して AI がどのトピックを選択するか確認します。
omni ai pick-topic your-model-id "How many users signed up last month?"
エージェント型クエリ(非同期)
Blobby の機能を存分に活用できます。AI が本番環境で実際に動作する場合と同様に、複数ステップの分析、ツールの使用、トピックの選択を行います。非同期処理のため、ジョブを送信し、ステータスをポーリングしてから、結果を取得します。
# 1. Submit a job
JID=$(omni ai job-submit your-model-id "Analyze revenue trends; identify our fastest growing category" -o json \
| python3 -c 'import json,sys;print(json.load(sys.stdin)["jobId"])')
# 2. Poll with EARLY EXIT on any terminal state — break on success AND on failure
while :; do
S=$(omni ai job-status "$JID" -o json | python3 -c 'import json,sys;print((json.load(sys.stdin).get("state") or "").lower())')
case "$S" in
complete*) break ;; # done → go fetch the result
fail*|cancel*|error*) echo "job $S"; break ;; # terminal failure → STOP, do not keep sleeping
*) sleep 5 ;; # only the non-terminal branch sleeps
esac
done
# 3. Get the result (resultSummary = narrative; actions[] = structured output)
omni ai job-result "$JID" -o json
ポーリングループでは、すべての終端状態で早期終了してください。成功時にしか終了しない、固定回数の
for … sleep … doneは使わないでください。 成功フィルターが誤っている(statusとstateの取り違え、COMPLETEとCOMPLETEDの取り違えなど)場合や、ジョブがエラーになった場合、そのようなループは最後まで実行されてしまいます。上記のwhile :; … case … break形式を使ってください。デフォルト分岐でのみスリープし、状態が終端状態(complete* または fail/cancel/error)になったら即座に終了します。フィールドは柔軟に読み取ってください(state ?? statusを小文字に変換し、startswith("complete")を完了とみなします)。これにより、以下のジョブ間で表記が異なっていても、同じループで処理できます。
ジョブステータスの形式。
stateをポーリングしてください。omni ai job-statusにstatusフィールドはありません。状態は、QUEUED→EXECUTING→DELIVERING→ 終端状態のCOMPLETE/FAILED/CANCELLEDです。COMPLETEDではなくCOMPLETEである点に注意してください。モデル更新(completed)とmodels jobs-get-status(COMPLETED)のフローでは表記が異なります。そのため、複数のジョブ種別で再利用するポーリングループには、柔軟な終端状態のチェックが必要です。フィールドをstate ?? statusとして読み取り、小文字に変換し、startswith("complete")を完了、{failed, cancelled, error}を失敗として扱います。回答テキストはresultSummary、構造化出力はactions[]の中にあります(type: "generate_query"→result.query)。
結果には、AI が実行した各ステップを含む actions 配列があります。生成されたクエリを取り出すには、type: "generate_query" のアクションを探してください。レスポンスには、AI による説明も resultSummary として含まれます。
非同期ジョブの回答を提示する前に、actions[] の各項目を確認してください。ジョブが COMPLETE に到達していても、個々の generate_query アクションの status が pending だったり、csvResult がなかったりする場合があります。その場合、説明文では生成されたクエリに触れていても、実際には実行されていない可能性があります。各アクションの status は、そのステップの終了状態を示します。complete、partial(要求された内容の一部を実行し、不足している内容を示す)、skipped、failed のいずれかです。これは result.status(success / error)とは別のもので、こちらは生成されたクエリが実行されたかどうかだけを示します。result.status: "success" の partial アクションでも、要求された内容のすべてに回答しているとは限りません。必要なアクションが pending、partial、または failed の場合、ジョブの概要を最終結果として扱わないでください。その特定のクエリを実行または再生成するか、別の非同期ジョブで同じ分析を続けてから、検証済みの結果のみを提示してください。
追加のジョブコマンド:
omni ai job-cancel <jobId>— 実行中のジョブをキャンセルしますomni ai job-visualization <jobId>— 可視化出力を取得しますomni ai job-feedback-submit <jobId> --body '{"rating":"good"}'— 完了したジョブへの高評価/低評価を記録します(CLI ≥ 1.2.2)。ratingは必須(good/bad)です。commentは任意の自由記述です。ポーリング結果がCOMPLETE/FAILEDになった後にのみ実行してください。それ以外の状態では 409 になります。追記専用で、読み戻すことはできないため、ジョブごとに一度だけ送信してください。組織スコープのキーを使う場合は、ジョブの送信に使用したユーザーにフィードバックを紐付けるため、--user-idを渡してください。
ダッシュボードでジョブの結果を使用する
ジョブの結果内のクエリオブジェクトは、ダッシュボードのqueryPresentationとしてそのまま使用できません。変換が必要です。主なルールは次のとおりです。
- 必ず
userEditedSQLを取り除いてください。これがあるとタイルが非トピッククエリになり、すべてのモデル制御(オブジェクトレベルのアクセス許可、行レベルのアクセスフィルター、always_where)が迂回され、ダッシュボードで制限付きロールから見えなくなります。また、含まれる${Order Items}というトピック名トークンも、ジョブ実行コンテキストの外では機能しません。 calculations[]が空でない場合は、userEditedSQLを取り除くだけで十分です。構造化された計算が正しくレンダリングされます。calculations[]が空の場合、Blobbyは計算式をインラインSQLとして記述しています。解析済みASTはcsvResultFields(resultレベルにあり、result["query"]内ではありません)で利用でき、適切なcalculations[]エントリーとして再構成できます。トップレベルのexpr演算子が集計(SUM、COUNTなど)であるフィールドは、テーブル計算として再構成できません。代わりに、フィルター付きメジャーとしてモデルに追加してください。
変換アルゴリズム、判別ロジック、フィールド参照の挿入、集計のスキップ処理、健全性チェックの方法について詳しくは、references/job-result-to-presentation.mdを参照してください。
どのアプローチをいつ使用するか
| アプローチ | 最適な用途 |
|---|---|
omni query run | 必要なフィールド、フィルター、ソートが正確に分かっている場合 |
omni query runとcalculations[] | テーブル計算を明示的に要求し、ASTの形式を把握しているかコピーできる場合 |
omni ai generate-query --run-query=false | 確認・手動編集用のシンプルなクエリASTの下書き。またはクエリを実行できない場合の形状確認のみ(エージェント型ジョブを実行できないときの代替手段) |
omni ai generate-query --run-query=true | 同期レスポンスが必要な、シンプルなディメンション/メジャークエリ |
omni ai job-submit | 単純でない処理全般――複数ステップの分析、またはテーブル計算/再利用可能なクエリASTの生成。actions[].generate_queryから構造化クエリ/calculationsを取り出し、query runで検証してください |
**求める形状が分かっている場合は、プロンプトで指定してください。**テーブル計算が望ましい出力、または正しいと分かっている出力である場合は、プロンプトに「… as a table calculation」を追加するか、「running total」/「% of total」/「moving average」など、求める意味を指定してください。これにより、エージェント型ジョブはuserEditedSQL/SQLの代替出力ではなく、actions[].generate_queryに実際の計算を出力します。(エージェント型とgenerate-queryの使い分け、および「actions[].generate_queryからASTを取り出し、query runで検証する」というルールについては、上の表とテーブル計算を参照してください。)
複数ステップ分析のパターン
複雑な分析では、クエリを連鎖させます。
- 広範なクエリ — データの形状を把握します
- 結果の確認 — 興味深いセグメントやパターンを特定します
- 対象を絞った追加分析 — 見つかった内容に基づいてフィルターします
- 統合 — 結果をまとめ、説明として提示します
よくあるクエリパターン
時系列:フィールド+日付ディメンション+昇順ソート+日付フィルター
上位N件:フィールド+メトリクス+降順ソート+上限
内訳付き集計:複数のディメンション+複数のメジャー+主要メトリクスによる降順ソート
既知のバグ
- 文字列のみのフィルター式は拒否されます。
"filters": { "field": "complete" | "last 90 days" | "not null" }は、2026年8月時点で400 "Unable to parse data stream"を返します(古いビルドでは500 "Cannot use 'in' operator to search for 'query_id' in <value>"を返していました。これはクエリ参照/field_name_in_queryのプローブです)。型指定されたフィルターオブジェクトを使用してください(上のフィルターを参照)。値、日付、null文字列、数値、ブール値の各形式で再現されています。
結果へのリンク
クエリは一時的なもので、クエリ結果の永続的な URL はありません。ユーザーに共有可能なリンクを提供するには、次のようにします。
- 既存のダッシュボードの場合:
{OMNI_BASE_URL}/dashboards/{identifier}(identifierはドキュメント API のレスポンスから取得します) - 新しい分析の場合:クエリを
queryPresentationとして指定し、omni-content-builderでドキュメントを作成してから、{OMNI_BASE_URL}/dashboards/{identifier}を共有します
ドキュメントの参照先
- クエリ API · ドキュメントクエリの実行 · クエリの使い方 · フィルター構文
関連スキル
- omni-model-explorer — クエリを実行する前にフィールドとトピックを見つけます
- omni-content-explorer — クエリを抽出できるダッシュボードを見つけます
- omni-content-builder — クエリ結果をダッシュボードに変換します
- omni-ai-eval — AI によるクエリ生成の精度をベンチマークしてテストします