Omni → Databricks Metric View
Converts an Omni topic into a Databricks Metric View by exploring the Omni model via API, translating its field definitions into the Databricks Metric View embedded YAML format, and executing via the Databricks CLI.
See FIELD-MAPPING.md for full before/after translation examples and YAML-REFERENCE.md for the complete YAML structure, aggregate type, and format mapping tables.
Prerequisites
bash
# 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."
bash
# 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>
bash
# Databricks CLI — verify installed and check profiles
databricks --version
cat ~/.databrickscfg
Tip: Use
to force structured output for programmatic parsing, or
for readable tables. The default is
(human in a TTY, JSON when piped).
Workflow
Step 1 — Gather Requirements
Ask the user:
- Which Omni topic do they want to convert? (e.g., )
- What is the Unity Catalog destination? () (e.g., )
- What is the Databricks SQL Warehouse ID? (run
databricks sql warehouses list
to find it)
- Is this a new metric view or does one already exist at
catalog.schema.[topic_name]_mv
?
- Which Databricks CLI profile to use (optional — only if the user has multiple profiles)?
⚠️
STOP — Confirm all answers before proceeding. The metric view will be named
by default.
Step 2 — Explore the Omni Model
2a. Find the model ID
bash
omni models list --modelkind SHARED
Identify the
Shared Model and note its
. Always prefer the Shared Model over Schema or Workbook models.
2b. Fetch the topic file
bash
omni models yaml-get <modelId> --filename <topic_name>.topic
From the topic file extract:
,
,
,
,
,
.
2c. Fetch the relationships file
bash
omni models yaml-get <modelId> --filename relationships
2d. Fetch each view file referenced in the topic
bash
omni models yaml-get <modelId> --filename <view_name>.view
If a view is prefixed with
, fetch the file starting with
. Skip any view backed by
— it has no physical table.
Step 3 — Identify Tables and Joins
Map view names to fully-qualified Databricks table references (
):
| Omni view name | Databricks table |
|---|
| catalog.ecomm.order_items
|
omni_dbt_ecomm__order_items
| catalog.ecomm.order_items
(strip ) |
The
separator maps to schema (left) and table (right). Confirm the catalog prefix with the user.
The
indentation defines the join chain — a view indented beneath another joins into its parent:
yaml
joins:
user_order_facts: {} # skip — derived CTE
ecomm__users: {} # joins to base_view
ecomm__inventory_items: # joins to base_view
ecomm__products: # joins to inventory_items
Find the dimension with
in each view — list it first among that table's dimensions.
✋ STOP — Confirm the full table list and join hierarchy with the user before continuing.
Step 4 — Resolve the Field List
| Syntax | Meaning |
|---|
| (no parameter) | Include all fields from all views |
| / | Include all fields from all views / named view |
| Include all fields with this tag |
| Include this specific field |
| Exclude this field (always wins over wildcard inclusions) |
Process inclusions first, then apply exclusions. Also remove any field with
unless explicitly included by name.
Step 5 — Build Join Definitions
Using the hierarchy from Step 3 and
, extract join columns from
and build the
clause. Use the view name as the join
.
Star schema (single-level):
yaml
joins:
- name: ecomm__users
source: catalog.ecomm.users
'on': source.user_id = ecomm__users.id
Snowflake schema (multi-hop):
yaml
joins:
- name: ecomm__inventory_items
source: catalog.ecomm.inventory_items
'on': source.inventory_item_id = ecomm__inventory_items.id
joins:
- name: ecomm__products
source: catalog.ecomm.products
'on': ecomm__inventory_items.product_id = ecomm__products.id
⚠️
is a YAML 1.1 reserved word —
always single-quote the key as
. Columns from nested (2+ level) joins
cannot be used in
— flatten them through a denormalized direct join instead.
Step 6 — Map Dimensions and Measures
For each field that survived Step 4, translate it using the rules below. See FIELD-MAPPING.md for full examples.
Dimension quick reference:
| Omni field type | Databricks translation |
|---|
| Standard string/number | |
| (no timeframes) | Single timestamp dimension |
| + | One dimension per timeframe |
| expression |
| range expression |
| DATEDIFF(unit, start, end)
expression |
| BOOLEAN dimension (not a filter; omit ) |
Measure quick reference:
| Omni measure type | Databricks translation |
|---|
aggregate_type: sum/avg/max/min
| / / etc. |
| |
aggregate_type: count_distinct
| |
| Derived (refs other measures) | MEASURE(measure_a) op MEASURE(measure_b)
— define atomics first |
| on a measure | AGG(col) FILTER (WHERE condition)
|
Strip Omni's
refs to bare column names (or
for joined fields). Use
for the Omni
,
for
, and carry
directly. See
YAML-REFERENCE.md for format and aggregate type mapping tables.
If the topic has
, carry it into the metric view's top-level
.
✋ STOP — Review all dimensions, measures, and join definitions with the user before generating the final output.
Step 7 — Check for Existing Metric View
bash
databricks api post /api/2.0/sql/statements \
--json "{\"warehouse_id\": \"<WAREHOUSE_ID>\", \"statement\": \"SHOW VIEWS IN <catalog>.<schema> LIKE '%_mv'\", \"wait_timeout\": \"30s\", \"catalog\": \"<CATALOG>\", \"schema\": \"<SCHEMA>\"}"
- View does not exist → use
CREATE OR REPLACE VIEW ... WITH METRICS
- View already exists → use
ALTER VIEW ... AS $$ ... $$
Step 8 — Generate and Execute the SQL
Write the SQL to a temp file:
sql
-- CREATE (new view)
CREATE OR REPLACE VIEW catalog.schema.orders_mv
WITH METRICS
LANGUAGE YAML
AS $$
version: 1.1
comment: "..."
source: catalog.ecomm.order_items
joins:
- name: ecomm__users
source: catalog.ecomm.users
'on': source.user_id = ecomm__users.id
dimensions:
- name: id
expr: id
display_name: "Order ID"
- name: status
expr: status
display_name: "Order Status"
measures:
- name: order_count
expr: COUNT(*)
display_name: "Order Count"
- name: total_sale_price
expr: SUM(sale_price)
display_name: "Total Sale Price"
format:
type: currency
currency_code: USD
$$
sql
-- ALTER (existing view)
ALTER VIEW catalog.schema.orders_mv AS $$
version: 1.1
...
$$
Execute via the SQL Statements API (
does not exist in CLI v0.295.0+):
bash
databricks api post /api/2.0/sql/statements \
--json "{
\"warehouse_id\": \"<WAREHOUSE_ID>\",
\"statement\": $(cat /tmp/orders_mv.sql | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))'),
\"wait_timeout\": \"50s\",
\"catalog\": \"<CATALOG>\",
\"schema\": \"<SCHEMA>\"
}"
Check the response for
. If
, read
and see the Troubleshooting section below.
✋ STOP — Confirm which group or user should receive access before running the GRANT. This is a permission change visible to others.
Grant access:
bash
databricks api post /api/2.0/sql/statements \
--json "{\"warehouse_id\": \"<WAREHOUSE_ID>\", \"statement\": \"GRANT SELECT ON VIEW catalog.schema.orders_mv TO \`group_name\`\", \"wait_timeout\": \"30s\", \"catalog\": \"<CATALOG>\", \"schema\": \"<SCHEMA>\"}"
Troubleshooting
When the SQL Statements API returns
, read
:
| Error message contains | Likely cause | Fix |
|---|
METRIC_VIEW_INVALID_VIEW_DEFINITION
| Invalid YAML field or value | Check the field name against the valid keys (, , , , , ). Common mistakes: using instead of , unsupported . |
| / | Warehouse is stopped or wrong ID | Start the warehouse in the Databricks UI or verify the ID with databricks api get /api/2.0/sql/warehouses
. |
| The CLI profile lacks privileges | Check the profile's permissions on the catalog/schema with databricks api get /api/2.0/unity-catalog/permissions/...
. |
| A source or join table doesn't exist in Unity Catalog | Verify each table reference with SHOW TABLES IN <catalog>.<schema>
. |
| parse error / unexpected key | not quoted | Always write (single-quoted) — it is a YAML 1.1 reserved word. |
| value error | Timeout out of range | must be between and . |
If the error message is truncated, run the same statement with
to get the full synchronous error response.
Critical Rules
- Naming: Name the metric view (snake_case, lowercase)
- CREATE vs ALTER: Check for existence first — for new, for existing
- Version: Always use (requires Databricks Runtime 17.2+)
- Skip derived CTEs: Views with have no physical table — skip and warn the user
- Confirm before executing: Show the full generated SQL to the user before running
- Boolean fields: Map as BOOLEAN dimensions — not filters. is not a valid field — omit it
- Composed measures: Use syntax; define atomic measures before composed ones
- YAML quoting: is a YAML 1.1 reserved word — always write (single-quoted)
- No SELECT *: All fields must be explicitly defined
- MAP columns: Skip joins to tables containing type columns — not supported
- Nested join refs: Only direct star join columns (1 level) can be used in . Flatten snowflake schema joins through a denormalized direct join
- Warehouse ID required: Always confirm before execution — cannot be inferred
- Exclusions win: always overrides any wildcard inclusion
- Format type values are lowercase: , , , , ,
- Date format required: and both require
- Currency format: Use not
- unsupported: Omit it entirely — causes a parse error
- CLI execution: Use
databricks api post /api/2.0/sql/statements
; must be –
- Omni CLI flag: Use (not )
- Field description key: Use not — is not a recognized field and causes a parse error
Reference