Labsco
exploreomni logo

omni-query

23

by exploreomni · part of exploreomni/omni-agent-skills

Run queries against Omni Analytics' semantic layer using the Omni CLI, interpret results, and chain queries for multi-step analysis. Use this skill whenever…

🔥🔥🔥✓ VerifiedFreeAdvanced setup
🧩 One of 7 skills in the exploreomni/omni-agent-skills package — works on its own, and pairs well with its siblings.

This is the playbook your agent receives when the skill activates — you don't need to read it to use the skill, but it's here to audit before installing.

by exploreomni

Run queries against Omni Analytics' semantic layer using the Omni CLI, interpret results, and chain queries for multi-step analysis. Use this skill whenever… npx skills add https://github.com/exploreomni/omni-agent-skills --skill omni-query Download ZIPGitHub23

Omni Query

Run queries against Omni's semantic layer via the Omni CLI. Omni translates field selections into optimized SQL — you specify what you want (dimensions, measures, filters), not how to get it.

Tip: Use omni-model-explorer first if you don't know the available topics and fields.

Discovering Commands

omni query --help # List query operations
omni query run --help # Show flags for running a query
omni query run --schema # Print the body's JSON schema + a filled example (no token)
omni ai --help # AI-powered query generation

Tip: Use -o json to force structured output for programmatic parsing, or -o human for readable tables. The default is auto (human in a TTY, JSON when piped).

Build queries on a topic

Prefer building every query on a topic, not a bare base view. Topics carry the governed joins, labels, and access — and a query not built on a topic is not accessible to restricted queriers/viewers (it works for you as a modeler/admin but silently fails for restricted roles). Set the query table to the topic's base view and pass join_paths_from_topic_name: <topic>.

How the join map resolves joined-view fields. table stays the topic's base view; join_paths_from_topic_name lets the topic's join map reach joined -view fields from it — e.g. to select users.state on an order_items-based topic, table stays order_items and the join comes from the topic; you do not set table: users. Omit join_paths_from_topic_name (or point table at a non-base view) and joined-view fields may fail to resolve or join wrong. Confirm the base view and every reachable join with omni models get-topic <modelId> <topic> — its base_view_name and join_via_map show the base view and the join path to each reachable view. (This is the canonical topic-query shape; omni-content-builder tiles and omni-model-builder validation queries use it too.)

Decide where the query should come from:

  • An existing topic answers it (its base view + a join-reachable view) → query that topic.

  • The field is on a join-reachable view but the topic doesn't expose it / lacks the join → propose extending the topic (add the relationship/join), then build it via omni-model-builder.

  • Fundamentally different subject, constraints, or audience → propose a new topic (see the "new topic vs extend" criteria in omni-model-builder). Prompt the requestor first, and build it on a branch.

Fallback — non-topic query pathways. Two pathways run outside any topic: a bare base view (table: + the global relationships file for joins) and raw SQL (userEditedSQL, see "Running Raw SQL"). Both share the same caveat: topic-scoped controls — access filters (row-level) and always_where — are not applied, and in a dashboard the tile is invisible to Viewer / Restricted Querier roles by default (handling a restricted audience is a content-permission concern — see omni-content-builder). The two pathways differ on object-level access grants: a bare-view query still enforces them, but raw SQL bypasses them too (it's the most permissive pathway). Prefer a topic when one fits; reach for a non-topic pathway only when nothing else expresses the query.

When the conclusion is "build or modify a topic," hand off to omni-model-builder to do it right.

Request-level options (outside query)

These keys sit at the top level of the body, beside query, not inside it:

Option Description resultType Output format: csv, xlsx, or json. Omit for the default base64 Arrow response. cache Cache policy: Standard, SkipRequery, SkipCache. userId Run as another user (org-scoped API keys); also the --userid flag. branchId Run against a model branch (validate draft model changes on live data). Must be a branch of the same shared model. planOnly Return the execution plan without running the query (validate/debug at no warehouse cost). Cannot combine with resultType. formatResults On exports, emit formatted values (e.g. $1,234.56) vs. raw. Requires resultType; ignored for Arrow. timezone Per-request timezone override (IANA id). Requires the connection setting allowsUserSpecificTimezones and the org setting allowsDocumentCanUseTimezoneOverride; silently no-ops if either is off.

Handling and Validating Results

Default response: base64-encoded Apache Arrow table. Arrow results are binary — you cannot parse individual row data from the raw response. To verify a query returned data, check summary.row_count in the response.

To read the results yourself (to validate or spot-check), request resultType: "csv" or "json" — both come back as text you can parse directly:

{ "query": { ... }, "resultType": "csv" }

resultType: "xlsx" is also valid, but it returns a binary .xlsx file (zip-based) — like the default Arrow blob, you can't read it inline without a spreadsheet app or a library. Use it only to deliver a file to a person, not to inspect results. For agent-side reading, stick to csv/json.

Result Validation

Every query response should be checked before trusting the results or presenting them to the user.

Check for errors:

  • If the response contains an error key, the query failed. Common causes: bad field name, missing join path, malformed filter expression, permission error.

  • If the response contains remaining_job_ids, the query is still running — poll with omni query wait before checking results.

Check row count:

  • summary.row_count == 0 — the query returned no data. This may be valid (e.g., no data in the filter range) but is worth flagging to the user. Common causes: overly restrictive filters, wrong date range, field that doesn't match any rows.

  • summary.row_count equals the limit you set — results may be truncated. If the user needs complete data, re-run with a higher limit or null for unlimited.

Spot-check data with CSV:

When accuracy matters, request CSV and scan the output:

omni query run --body '{
 "query": { ... },
 "resultType": "csv"
}'

Check that:

  • Column headers match the fields you requested

  • Values are in expected ranges (e.g., revenue isn't negative, dates aren't in the future)

  • Aggregations make sense (e.g., a count isn't returning a sum)

Validate filter behavior:

If your query includes filters, verify they're being applied:

# Run the same query without filters
omni query run --body '{ "query": { ... (no filters) ... }, "resultType": "csv" }'

# Compare row counts — filtered should be If both queries return the same row count, the filter may not be binding (wrong field name, unsupported expression, or the known bug where boolean filters are dropped with pivots).

### Validation Checklist

 Check How When 
 No error in response Check for `error` key Every query 
 Data was returned `summary.row_count > 0` Every query 
 Results not truncated `row_count < limit` When completeness matters 
 Columns are correct CSV column headers match requested fields When building dashboards or reports 
 Values are reasonable Spot-check CSV output When presenting to users 
 Filters are applied Compare filtered vs unfiltered row counts When using filters 
 Long-running query completed No `remaining_job_ids` in final response Queries on large tables 
 

### Decoding Arrow Results

import base64, pyarrow as pa arrow_bytes = base64.b64decode(response["data"]) reader = pa.ipc.open_stream(arrow_bytes) df = reader.read_all().to_pandas()


### Long-Running Queries

 If the response includes `remaining_job_ids`, poll until complete:

omni query wait --jobids job-id-1,job-id-2


## AI-Powered Query Generation

Instead of constructing query JSON manually, you can describe what you want in natural language and let Omni's AI generate the query.

### Generate Query (synchronous)

 The fastest path — returns a generated query JSON synchronously. Pass `--run-query false` to get only the query structure without executing it (default runs the query).

Just generate the query JSON (no execution)

omni ai generate-query your-model-id "Show me revenue by month" --run-query false


 Response:

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


 Optional flags:

 

- `--branch-id` — test against a specific model branch 

- `--current-topic-name` — constrain topic selection to a specific topic 

### Pick Topic

 Check which topic the AI would select for a question, without generating a full query:

omni ai pick-topic your-model-id "How many users signed up last month?"


### Agentic Queries (async)

 For the full Blobby experience — multi-step analysis, tool use, and topic selection as the AI would actually behave in production. This is async: submit a job, poll for status, then retrieve the result.

1. Submit a job

omni ai job-submit your-model-id "Analyze revenue trends and identify our fastest growing product category"

→ returns { "jobId": "job-uuid", "conversationId": "conv-uuid" }

2. Poll the state field (NOT status — reading .status is empty every time)

omni ai job-status

3. Get the result

omni ai job-result


 
 **Job-status shape.** Poll **`state`** — `omni ai job-status` has **no `status` field**. States: `QUEUED` → `EXECUTING` → `DELIVERING` → terminal **`COMPLETE`** / `FAILED` / `CANCELLED`. Note it's `COMPLETE`, **not** `COMPLETED` — the model-refresh (`completed`) and `models jobs-get-status` (`COMPLETED`) flows spell it differently, so a poll loop reused across job types needs a **tolerant terminal check**: read the field as `state ?? status`, lowercase it, treat `startswith("complete")` as done and `{failed, cancelled, error}` as failed. The answer text is **`resultSummary`**; structured output is under **`actions[]`** (`type: "generate_query"` → `result.query`).

 
 The result contains an `actions` array with each step the AI took — look for actions with `type: "generate_query"` to extract the generated queries. The response also includes `resultSummary` with the AI's narrative interpretation.

 Before presenting an async job answer, inspect the `actions[]` entries. A job can reach `COMPLETE` while an individual `generate_query` action has `status: "pending"` or no `csvResult`; the narrative may then describe a query that was generated but not executed. If a required action is pending, do not treat the job summary as final. Run or regenerate that specific query, or continue the same analysis with another async job, then present only validated results.

 Additional job commands:

 

- `omni ai job-cancel <jobId>` — cancel a running job 

- `omni ai job-visualization <jobId>` — get the visualization output 

### Using Job Results in a Dashboard

 The query object inside a job result is **not directly usable** as a dashboard `queryPresentation` — it requires a transformation. Key rules:

 

- **Always strip `userEditedSQL`** — it makes the tile a non-topic query, so it bypasses **all** model controls (object-level access grants, row-level access filters, and `always_where`) and is invisible to restricted roles in a dashboard. The `${Order Items}` topic-name token it contains also fails outside the job execution context. 

- **When `calculations[]` is non-empty**, stripping `userEditedSQL` is sufficient — the structured calc renders correctly. 

- **When `calculations[]` is empty**, Blobby authored the calc as inline SQL. The parsed AST is available in `csvResultFields` (at `result` level, not inside `result["query"]`) and can be reconstructed as a proper `calculations[]` entry. Fields whose top-level expr operator is an aggregate (`SUM`, `COUNT`, etc.) cannot be reconstructed as table calcs — add them to the model as filtered measures instead. 

 For the complete transformation algorithm, discriminator logic, field-ref injection, aggregate-skip handling, and sanity-check approach, see [references/job-result-to-presentation.md](https://github.com/exploreomni/omni-agent-skills/blob/main/skills/omni-query/references/job-result-to-presentation.md).

### When to Use Which Approach

 Approach Best For 
 `omni query run` You know exactly which fields, filters, and sorts you need 
 `omni query run` with `calculations[]` Explicit table-calculation requests where you know or can copy the AST shape 
 `omni ai generate-query --run-query=false` Drafting a **simple** query AST to inspect/hand-edit; **or shape-only when query execution isn't permitted** (the fallback when you can't run an agentic job) 
 `omni ai generate-query --run-query=true` Simple dimension/measure queries where you want a synchronous response 
 `omni ai job-submit` **Anything non-trivial** — multi-step analysis, **or generating a table calc / reusable query AST**. Lift the structured query/`calculations` from `actions[].generate_query`, then validate with `query run` 
 

 **Steer the prompt when you know the shape:** when a table calculation is the desired or known-correct output, say so in the prompt — append "… as a table calculation ", or name the semantics ("running total" / "% of total" / "moving average") — so the agentic job emits a real calc in `actions[].generate_query` rather than a `userEditedSQL`/SQL fallback. (The agentic-vs-`generate-query` split and the "lift the AST from `actions[].generate_query`, then validate with `query run`" rule are in the table above and under Table Calculations .)

## Multi-Step Analysis Pattern

For complex analysis, chain queries:

 

- **Broad query** — understand the shape of the data 

- **Inspect results** — identify interesting segments or patterns 

- **Focused follow-ups** — filter based on findings 

- **Synthesize** — combine results into a narrative

## Common Query Patterns

**Time Series**: fields + date dimension + ascending sort + date filter

 **Top N**: fields + metric + descending sort + limit

 **Aggregation with Breakdown**: multiple dimensions + multiple measures + descending sort by key metric

## Known Bugs

- **`IS_NOT_NULL` filter generates `IS NULL`** (reported Omni bug) — workaround: invert the filter logic or use the base view to apply the filter differently. 

- **Boolean filters may be silently dropped** when a `pivots` array is present — if boolean filters aren't applying, remove the pivot and test again. 

- **Some natural-language date filter strings can hit `query_id` parser errors** — retry with the typed date filter object shape shown above before abandoning the filter.

## Linking to Results

Queries are ephemeral — there is no persistent URL for a query result. To give the user a shareable link:

 

- **For existing dashboards**: `{OMNI_BASE_URL}/dashboards/{identifier}` (the `identifier` comes from the document API response) 

- **For new analysis**: Create a document via `omni-content-builder` with the query as a `queryPresentation`, then share `{OMNI_BASE_URL}/dashboards/{identifier}`

## Docs Reference

- [Query API](https://docs.omni.co/api/queries.md) · [Running Document Queries](https://docs.omni.co/guides/run-document-queries.md) · [Querying Documentation](https://docs.omni.co/analyze-explore/querying.md) · [Filter Syntax](https://docs.omni.co/modeling/filters.md)

## Related Skills

- **omni-model-explorer** — discover fields and topics before querying 

- **omni-content-explorer** — find dashboards whose queries you can extract 

- **omni-content-builder** — turn query results into dashboards 

- **omni-ai-eval** — benchmark and test AI query generation accuracy