[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M7C2KKSEMBS5180STDMSVTDS":3,"skill-same:01M7C2KKSEMBS5180STDMSVTDS":52},{"skillNo":4,"name":5,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":6,"repositoryNo":11,"starsCount":12,"licenseSpdxId":13,"licenseFileUrl":6,"sourceHtmlUrl":14,"categories":15,"keywords":16,"publishedAt":17,"linkOnly":18,"markdownContent":19,"markdownBody":20,"frontMatter":21,"refName":22,"commitSha":23,"skillDirectory":24,"forksCount":12,"repositoryHtmlUrl":25,"repositoryHomepageUrl":6,"ownerHtmlUrl":26,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":27,"install":28,"resources":41},"01M7C2KKSEMBS5180STDMSVTDS","metrics",null,"kirivandubai\u002Fdenodo_wipe_coading_skills","kirivandubai","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F221521416?v=4","01M7C2HC9KETA166XKR09YMKZQ","01M7C2HCAJ787NNYKMCG12S3DY",0,"MIT","https:\u002F\u002Fgithub.com\u002Fkirivandubai\u002Fdenodo_wipe_coading_skills\u002Fblob\u002Fmain\u002Fskills\u002Fmetrics\u002FSKILL.md",[],[],"2026-11-15 14:19:17",false,"---\nname: metrics\ndescription: Use when business metrics or KPIs have to be defined once in Denodo 9.5 and reused by BI tools and AI agents — a metric view over a fact view and its dimension views — and whenever a metric view is queried or built on: EVALUATE_METRIC, a view over a metric view, metrics of two metric views side by side, a share, a ratio, year over year, to date, a running total. Also for \"a semantic layer of KPIs\", \"one definition of revenue for every tool\", and for the symptoms — AVG or SUM over a metric returns the metric's own value, SELECT * from a metric view returns no rows, a query joining a metric view runs until it times out, the totals by a dimension do not add up to the grand total, \"Error applying metric transformation\". Not for a mart of fixed grain that one consumer reads — \u002Fdenodo:views; not for descriptions, keys and tags of views that already exist — \u002Fdenodo:semantics.\n---\n\n# Metric views: KPIs defined once, sliced by any dimension\n\nA **metric view** (new in 9.5) declares a model instead of a query: one **fact view** that\nholds the measures, the **dimension views** around it, the **associations** that join them,\nthe **dimensions** a consumer may group and filter by, and the **metrics** — aggregations\nover the fact view. A query names the dimensions it wants, and Denodo builds the joins and\nthe aggregation for exactly those, so \"revenue\" is the same number in every tool at every\ngrain. BI tools, the Data Marketplace, Assisted Query and the MCP Server read it like any\nview, telling dimensions from metrics.\n\nThe model does not know this object, and most of what goes wrong with it returns a number,\nnot an error. Associations and the join rules for any two views are `\u002Fdenodo:views`; the\ndescriptions consumers read are `\u002Fdenodo:semantics`. Applying files is `\u002Fdenodo:execute`;\nthe working loop, the naming defaults and the safety rule are `\u002Fdenodo:vql`. A **mart** —\none aggregate of fixed grain for one consumer — is still a `CREATE VIEW … GROUP BY` in\n`\u002Fdenodo:views`; a metric view is for figures many consumers slice their own way.\n\n## The rule: a metric view stands alone in its `FROM`\n\nThe only thing built directly on a metric view is a **selection view** — the metric view\nalone in its `FROM`, dimensions in `SELECT` and `GROUP BY`, each metric as\n`evaluate_metric(\u003Cmetric>)`, a filter on dimensions in `WHERE`. Everything else — another\ndimension view, another fact, a second metric view, arithmetic over metrics, a total — is\ndone over selection views. Measured on 9.5.1, none of these says what is wrong: a join in the\nsame `FROM` runs until the query timeout; arithmetic or `ROUND` around `evaluate_metric`, or\n`SUM`\u002F`AVG` around a metric (`AVG(revenue)` returns the sum), is ignored, in a query and\ninside a `CREATE VIEW` alike; a ratio of two `evaluate_metric` returns no rows;\n`evaluate_metric` over another view is `NULL` (Silent failures, 1-3 and 10; Common mistakes,\nrow 1).\n\nOver a selection view — a view or a subquery — joins, `ROUND`, ratios and `CASE` work as in\nany view.\n\n## 1. The model, before the statement\n\n| Part | What it must be | Check |\n|---|---|---|\n| **Fact view** | the one view every metric reads — metrics over two views are refused | its grain: `SELECT COUNT(*), COUNT(DISTINCT \u003Ckey>) FROM \u003Cfact>` |\n| **Dimension views** | one row per key, and that key declared as the view's `PRIMARY KEY` — a duplicated key multiplies every metric under it; an undeclared one can empty every `HAVING` grouped by it over a file source (Silent failures, 12) | `SELECT COUNT(*), COUNT(DISTINCT \u003Ckey>) FROM \u003Cdimension>` — equal; `column_is_primary_key` in `GET_VIEW_COLUMNS()` |\n| **Associations** | one per dimension, from the fact (or from the dimension a snowflake hangs on), a tree with no second path | `SELECT association_name, mappings, valid FROM GET_ASSOCIATIONS() WHERE input_database_name = '\u003Cdb>' AND input_type = 'views' AND input_name = '\u003Cfact>'` |\n| **Fact rows without a dimension row** | decide: kept in a `NULL` group, or dropped | `SELECT COUNT(*), COUNT(\u003Cfk>) FROM \u003Cfact>`, and orphans: `… LEFT OUTER JOIN \u003Cdimension> … WHERE \u003Cfk> IS NOT NULL AND \u003Cdimension key> IS NULL` |\n\nThe checks in the table — *verified: 9.5.1 (live, 2026-10-05)*.\n\n**Which fact rows survive is decided by the association, not by the query** — *verified:\n9.5.1 (live, 2026-10-01)*:\n\n| In `ASSOCIATIONS` | Rows of the fact without a match | Dimension members without facts |\n|---|---|---|\n| `RIGHT`, dimension endpoint `(0,1)` or `PRINCIPAL (0,1)` | kept, in a `NULL` group of that dimension | absent |\n| `LEFT` | **dropped** | present, metrics `NULL` (`COUNT` 0) |\n| `INNER`, or `RIGHT` over a `(1)` endpoint | **dropped** | absent |\n| nothing | from the association: dimension endpoint `(1)` → as `INNER`; `PRINCIPAL (0,1)` → as `LEFT` | |\n\n- **The association template in `\u002Fdenodo:views` declares `(1)`**, under which `RIGHT` runs as\n  `INNER`: right when the check above finds no `NULL` key and no orphan, wrong otherwise — then\n  the association says `(0,1)`: `ENDPOINT \u003Crole> \u003Cfact view> (0,*)` and\n  `ENDPOINT \u003Crole> \u003Cdimension view> PRINCIPAL (0,1)`, the rest of the template unchanged.\n- The order of the two aliases does not matter; the type is read against the fact.\n- A query without any dimension of a group applies no join for it: the grand total always\n  counts every fact row, which is why a model that drops rows still shows the right total.\n- **Another association between the fact and the view under a dimension view** (the base\n  view a dimension `iv_` selects from) turned `RIGHT` into `INNER`; dropping it restores\n  `RIGHT`. Before you add an association near a metric view, run Verify.\n- An association between views you did not create is a proposal to their owner\n  (`\u002Fdenodo:semantics`). Over someone else's base views, build your own `iv_` views for the\n  fact and each dimension (`\u002Fdenodo:views`) — with the key declared, text trimmed — and put\n  the associations between those: everything the model needs is then yours, and no\n  association lands next to theirs. The metric view can also live in your own database and\n  name views of another (`\u003Cdb>.\u003Cview>` in `SOURCES`).\n\n## 2. Templates\n\nEach template is one file, applied whole. `CREATE OR REPLACE METRIC VIEW` re-applies safely;\nviews built on it stay valid while the metrics and dimensions they name stay.\n\n### The metric view\n\n```sql\n-- verified: 9.5.1 (live, 2026-10-07)\nCONNECT DATABASE sales_analytics;\n\nCREATE OR REPLACE METRIC VIEW household_metrics\n    FOLDER = '\u002F03 - business entities'\n    DESCRIPTION = 'Household KPIs by income band and buying potential. Query: SELECT \u003Cdimensions>, evaluate_metric(\u003Cmetric>) FROM household_metrics GROUP BY \u003Cdimensions>.'\n    (\n        buy_potential   ( description = 'Buying potential band of the household, as in the source (for example ''0-500'').' ),\n        household_count ( description = 'Number of households.' ),\n        avg_dependents  ( description = 'Average number of dependents per household.' )\n    )\n    SOURCES (\n        households AS bv_household_demographics,\n        bands      AS bv_income_band\n    )\n    ASSOCIATIONS (\n        households bands a_income_band_household RIGHT\n    )\n    DIMENSIONS (\n        income_band (\n            income_band_sk     AS bands.ib_income_band_sk,\n            income_lower_bound AS bands.ib_lower_bound,\n            income_upper_bound AS bands.ib_upper_bound\n        ),\n        household (\n            buy_potential AS TRIM(households.hd_buy_potential)\n        )\n    )\n    METRICS (\n        household_count AS COUNT(households.hd_demo_sk),\n        dependents      AS SUM(CAST('long', households.hd_dep_count)),\n        avg_dependents  AS AVG(households.hd_dep_count)\n    );\n```\n\nClause order: `FOLDER` → `DESCRIPTION` → `TAGS` → `( field properties )` → `SOURCES` →\n`ASSOCIATIONS` → `DIMENSIONS` → `METRICS`. Each alias in `SOURCES` is how dimensions and\nmetrics name that view. `a_income_band_household` is the association template of\n`\u002Fdenodo:views`; its `(1)` endpoint makes this `RIGHT` run as `INNER`, which is right here\nbecause every household has its band. `bv_income_band` declares its `PRIMARY KEY` in its own\nfile (Silent failures, 12).\n\n- **A dimension** is any expression over the sources without an aggregate: `TRIM`, `CASE`,\n  `COALESCE(\u003Cdim>.\u003Clabel>, '(not specified)')` to name the `NULL` group, `GETYEAR(\u003Cdate>)`, a\n  `CONCAT` over two views. Labels and buckets belong here, in the definition (Silent failures,\n  13). Groups (`income_band ( … )`) are how consumers see them; an attribute after the last\n  group, `, \u003Cname> AS \u003Cexpr>`, is a generic one.\n- **A metric** is an aggregate over fact columns: `SUM`, `COUNT(\u003Ccolumn>)`, `COUNT(DISTINCT\n  …)`, `AVG`, `MIN`, `MAX`, with `CASE` inside (`SUM(CASE WHEN … THEN amount ELSE 0 END)`) and\n  arithmetic over aggregates (`SUM(fee) \u002F NULLIF(SUM(amount), 0)`). It cannot name another\n  metric or use `COUNT(*)` — both are `invalid field: missing source schema for field:\n  \u003Cmetric>`; repeat the aggregates instead.\n- **The dialect rules hold inside a metric** (`\u002Fdenodo:vql`, **Expressions: VQL is not\n  PostgreSQL**): `SUM` over an `int` column stays `int`, hence `CAST('long', …)` above — never\n  over a `decimal`, which it truncates; an `int` divided by an `int` is an integer division\n  (Silent failures, 11). `ROUND` goes inside the metric (`ROUND(AVG(x), 2)`) or over a\n  selection view's column.\n- **`AVG(x)` and `COUNT(x)` skip `NULL`s**: say in the metric's description whether the KPI is\n  `AVG(x)` or `SUM(x) \u002F COUNT(\u003Ckey>)`, a missing value counted as zero.\n- **A filter that every metric obeys goes into the fact view** — an `iv_` derived view with\n  the `WHERE`, named in `SOURCES`. The `FILTER ( … )` clause the Design Studio wizard writes\n  after `SOURCES` fails on some queries with `Error applying metric transformation.` —\n  details in `references\u002Fmetric-views.md`.\n- Field descriptions go in the field properties above or by `ALTER VIEW \u003Cmetric view> (\n  ALTER COLUMN … ADD ( DESCRIPTION = '…' ) )`. A selection view inherits the description of\n  a dimension it passes through, never that of a metric: describe `evaluate_metric` columns\n  in the selection view (`\u002Fdenodo:semantics`).\n- `DESC VQL` returns `CREATE METRIC VIEW` without `OR REPLACE` and without `CONTEXT`: a\n  reference, not your file. A metric view takes no cache\n  (`Metric views do not support cache mode`): cache its sources (`\u002Fdenodo:cache`, **Templates**).\n\n### Selection views, a total and a share\n\n```sql\n-- verified: 9.5.1 (live, 2026-10-07)\nCONNECT DATABASE sales_analytics;\n\nCREATE OR REPLACE VIEW households_by_band\n    FOLDER = '\u002F03 - business entities'\n    DESCRIPTION = 'Households per income band, from the metrics of household_metrics. One row per income band.'\n    AS SELECT income_band_sk,\n              evaluate_metric(household_count) AS household_count,\n              evaluate_metric(avg_dependents)  AS avg_dependents\n       FROM household_metrics\n       GROUP BY income_band_sk\n    CONTEXT ('formatted' = 'yes');\n\nCREATE OR REPLACE VIEW households_total\n    FOLDER = '\u002F03 - business entities'\n    DESCRIPTION = 'All households, from the metrics of household_metrics. One row.'\n    AS SELECT evaluate_metric(household_count) AS household_count,\n              evaluate_metric(avg_dependents)  AS avg_dependents\n       FROM household_metrics\n    CONTEXT ('formatted' = 'yes');\n\nCREATE OR REPLACE VIEW household_share_by_band\n    FOLDER = '\u002F03 - business entities'\n    DESCRIPTION = 'Each income band''s share of all households, in percent. One row per income band.'\n    AS SELECT b.income_band_sk,\n              b.household_count,\n              ROUND(100.0 * b.household_count \u002F t.household_count, 2) AS household_share_pct,\n              ROUND(b.avg_dependents, 2)                              AS avg_dependents\n       FROM households_by_band b\n            CROSS JOIN households_total t\n    CONTEXT ('formatted' = 'yes');\n```\n\n- **A total comes from the metric, never from adding up rows.** `households_total` asks the\n  metric view with no dimension at all — allowed when the `SELECT` holds metrics only; a\n  literal next to them needs a `GROUP BY` (`For this type of query, the group by fields are\n  mandatory.`). Summing the rows of `households_by_band` is right for a sum or a count and\n  wrong for an average (an average of averages) and for a `COUNT(DISTINCT …)` (a customer in\n  two bands counts twice); for a coarser grain ask the metric view at that grain.\n- **A view or fact from outside the model** joins to a selection view grouped by the join\n  key — `GROUP BY \u003Ckey>` in the selection, then `JOIN \u003Cother view> ON s.\u003Ckey> = …`. **Two\n  metric views side by side**: one selection view each, at the same dimensions — aliased to\n  one name when the two metric views call it differently — joined on them; a `FULL OUTER\n  JOIN` keeps a member only one of them has, and the key column is then `COALESCE(a.\u003Ckey>,\n  b.\u003Ckey>)`.\n- Arithmetic over metrics — a ratio, a percent, rounding — is a column of the view over the\n  selections, as `household_share_pct` above.\n- **An average per member** — per customer, per store — is a selection grouped by the\n  member, averaged in the view above: `SELECT type, AVG(m.total) FROM (SELECT type,\n  customer_id, evaluate_metric(revenue) AS total … GROUP BY type, customer_id) m GROUP BY\n  type` — *verified: 9.5.1 (live, 2026-10-05)*. Its denominator is the members that have facts; members without any are absent\n  under `RIGHT`. When they must count as zero, take the members from a dimensions-only query\n  (it lists every one) and `LEFT OUTER JOIN` the totals to it.\n- **When nothing may be created** — a question to answer, not a view to build — the same\n  views are subqueries: `SELECT … FROM (\u003Cselection>) b CROSS JOIN (\u003Ctotal>) t`.\n\n### Periods: year over year, to date, running totals\n\nRead `references\u002Fperiods.md` before any year-over-year, to-date or running-total figure: its\ntemplates, and what decides them — a partial first or last period, which every total check\npasses; a coarser period, which adds up only sums and counts; \"today\", the server's clock and not\nthe data's; the year before, a join and not `LAG`.\n\n### Query it ad hoc\n\n```sql\n-- verified: 9.5.1 (live, 2026-10-07)\nSELECT income_band_sk, buy_potential,\n       evaluate_metric(household_count) AS household_count\n  FROM household_metrics\n WHERE buy_potential = '>10000'\n GROUP BY income_band_sk, buy_potential\nHAVING evaluate_metric(household_count) > 50\n ORDER BY household_count DESC;\n```\n\n| Works | Does not |\n|---|---|\n| `WHERE` on dimensions | `WHERE evaluate_metric(m) > …` — `Aggregate functions are not allowed here`; `WHERE \u003Cmetric> > …` — an `Error executing query` from the route |\n| `HAVING evaluate_metric(m) > …`, `HAVING \u003Cdimension> …` | `HAVING \u003Calias>` — `Field not found`; `HAVING \u003Cmetric>` — `is not a group by field` |\n| `ORDER BY \u003Calias>` or `ORDER BY evaluate_metric(m)` | `ORDER BY` a field not in the `SELECT` — `Field not found '\u003Cf>' in view with schema` |\n| an expression over a dimension in `SELECT`, grouped by the **raw dimension** — one row per raw value, every metric kept | the same expression grouped by it or by its **alias** (Silent failures, 13) |\n| `COUNT(*)` — the fact rows of the group | `GROUP BY \u003Cmetric>` — `The group by fields can only be dimensions or literals` |\n| `LIMIT` | no `GROUP BY` with a dimension in `SELECT`, or `SELECT *` — **0 rows, no error** |\n\nA query of dimensions only lists every member of the dimension view — every year of a\ncalendar — not the members that have facts. Ask with a metric to see what has data.\n\n## 3. Read what exists\n\n```sql\n-- verified: 9.5.1 (live, 2026-10-07)\nSELECT column_name, column_organization, column_dimension, column_definition, column_vdp_type\n  FROM GET_VIEW_COLUMNS()\n WHERE input_database_name = 'sales_analytics' AND input_view_name = 'household_metrics';\n\nSELECT name, sources, associations, filter\n  FROM GET_METRIC_VIEWS('sales_analytics', 'household_metrics');\n```\n\nThe first lists every dimension and metric with its expression — what a query may name;\nadd `column_remarks` for the descriptions.\nThe second gives the sources and, per association, the join type: `0` `INNER`, `1` `LEFT`,\n`2` `RIGHT`, `null` none written. `GET_METRIC_VIEWS` takes a `LIKE` pattern only as a\npositional argument; `WHERE input_name = '\u003Cpattern>'` returns nothing. These two are the way\nto read a metric view; `vql desc … --vql` of it, or of a view built on it, brings back its data\nsources and their encrypted passwords (`\u002Fdenodo:execute`, **Commands**).\n\n## Who applies what\n\nA new metric view and the views over it, created in this session, are yours to apply and\nverify. Changing a metric view you did not create in this session, even to add one metric:\nshow the file and get a yes (`\u002Fdenodo:vql`). A missing dimension or metric in another team's\nmetric view is a proposal to them; until they add it, your own metric view over the same\nsources answers the question without touching theirs.\n\n## Verify — the totals check\n\nThe one check that catches the silent failures of the model: the same figure three ways.\n\n```sql\n-- verified: 9.5.1 (live, 2026-10-07)\nCONNECT DATABASE sales_analytics;\n\n-- 1. The fact itself.\nSELECT COUNT(hd_demo_sk) AS fact_total FROM bv_household_demographics;\n\n-- 2. The metric with no dimension: no join is applied.\nSELECT evaluate_metric(household_count) AS metric_total FROM household_metrics;\n\n-- 3. Once per dimension group: its slices add up to the same total.\nSELECT SUM(s.household_count) AS slices_total, COUNT(*) AS slices\n  FROM (SELECT income_band_sk, evaluate_metric(household_count) AS household_count\n          FROM household_metrics\n         GROUP BY income_band_sk) s;\n```\n\n| You see | It means |\n|---|---|\n| 1 = 2 = 3 | every fact row is in every slicing |\n| 3 lower than 2 | fact rows dropped under that dimension: `NULL` or orphan keys with `INNER`, `LEFT`, no type, or a `(1)` endpoint (section 1) |\n| 3 higher than 2 | a key duplicated in that dimension view: those facts are counted twice |\n| 2 differs from 1 | the metric reads a different column or a `FILTER`; read its definition (section 3) |\n\n**On a metric view you did not build** the same three queries tell you what its\nassociations do with the rows: you cannot read the join from its definition alone.\n\nRun step 3 for one attribute of each dimension group — each association is applied only\nwhen a query names its dimensions. Use a sum or a count for it; then compare every other\nmetric with plain SQL over the fact and the dimension — one `FULL OUTER JOIN` of the\nselection and the plain `GROUP BY` on the dimensions, keeping the rows where any figure\ndiffers, checks every slice at once. Compare averages with a tolerance: the metric and\nhand-written SQL may type them differently and differ in the last digit. Also: `SELECT\nname, view_status FROM GET_VIEWS() WHERE input_database_name = '\u003Cdb>' AND\ninput_retrieve_invalid_views_only = true` is empty, and every selection view returns rows —\n*verified: 9.5.1 (live, 2026-10-05)*.\n\n## Silent failures\n\nEach runs without an error — *verified: 9.5.1 (live, 2026-10-01)*.\n\n| You wrote | What you get | Instead |\n|---|---|---|\n| 1. `AVG(\u003Csum metric>)`, `SUM(\u003Cavg metric>)`, `MAX(…)` | the metric's own aggregation | `evaluate_metric`; another aggregation is another metric, or a view over a selection |\n| 2. `evaluate_metric(a) * 100`, `ROUND(evaluate_metric(a), 2)` | `a`, unchanged | the expression over the selection view's column |\n| 3. `evaluate_metric(a) \u002F evaluate_metric(b)` | no rows | a ratio metric `SUM(x) \u002F NULLIF(SUM(y), 0)`, or the division over a selection |\n| 4. `SELECT * FROM \u003Cmetric view>`, a dimension without `GROUP BY` | no rows | dimensions in `GROUP BY`, metrics in `evaluate_metric` |\n| 5. `RIGHT` over an association with a `(1)` dimension endpoint | facts without a match dropped from every grouped query; the grand total still right | `(0,1)` on the association; the totals check |\n| 6. `LEFT`, or no type over a `PRINCIPAL (0,1)` endpoint | every dimension member, with empty metrics, and the unmatched facts dropped | `RIGHT` |\n| 7. a dimension view with a duplicated key | every metric of that key multiplied | one row per key in the dimension view |\n| 8. a metric without an aggregate (`n AS f.qty`) | accepted; every query with it returns no rows | an aggregate |\n| 9. the sum of a selection's rows as a total | an average of averages; distinct counts counted twice | the metric with no dimension |\n| 10. `evaluate_metric(x)` over a selection view | `NULL` | `x` — `evaluate_metric` belongs to the metric view only |\n| 11. a share as `SUM(CASE … 1 ELSE 0 END) \u002F COUNT(x)` | `0` — integer division | `1.0 * SUM(…) \u002F COUNT(x)` |\n| 12. `HAVING evaluate_metric(m) > …` grouped by the key of a dimension view that declares no `PRIMARY KEY`, over a file source | no rows: the plan is `INCOMPATIBLE_QUERY_VIEW`, `VOID PLAN` (the same query delegated to SQL Server answered) | declare the key in the dimension view's file; or filter the selection view's column in a view above |\n| 13. `COALESCE(\u003Cdim>, '…') AS d`, `UPPER(\u003Cdim>) AS d`, `GETYEAR(\u003Cdate>)` or a `CASE` in a query, grouped by it or by its alias, over sources delegated to a database (measured on SQL Server) | every `COUNT(DISTINCT …)` metric missing from the result — the plan already projects without it; sums, counts and averages stay (over file sources it stayed) — the expression grouped as itself: *verified: 9.5.1 (live, 2026-10-06)* | the expression in the dimension's definition, or `GROUP BY \u003Cdim>` and the label in the view above |\n| 14. a query over a selection view that reads none of its metric columns — `SELECT MIN(\u003Cdate>)`, a `COUNT(*)` | every member of the dimension view, with facts or without: a calendar's every day — *verified: 9.5.1 (live, 2026-10-06)* | read the metric in the same query: `WHERE \u003Cmetric column> > 0` |\n\n## Common mistakes\n\n| You wrote | Server says | Fix |\n|---|---|---|\n| a metric view joined to a view in one `FROM` | nothing, then `Error: Time out processing data` | a selection view, then the join |\n| `per AS total \u002F cnt` — a metric over metrics; `COUNT(*)` | `invalid field: missing source schema for field: per` | repeat the aggregates; `COUNT(\u003Cfact column>)` |\n| `MAX(…)` in a dimension | `The following fields cannot be projected: max(…) AS \u003Cdim>` | aggregates go in `METRICS` |\n| metrics over two source views | `There are metric fields referencing different views. Metrics must be built using fields from the same view.` | one fact view; the other figure is a second metric view |\n| two `SOURCES`, no `ASSOCIATIONS` | `invalid associations: It can't be empty` | name the association |\n| `evaluate_metric(\u003Cdimension>)` | `There are dimensions fields with function aggregation …` | dimensions go in `GROUP BY` |\n| a literal next to metrics, no `GROUP BY` | `For this type of query, the group by fields are mandatory.` | `GROUP BY '\u003Cliteral>'`, or the literal in the view above |\n| a view `AS SELECT * FROM \u003Cmetric view>` | created; its queries fail with `Error applying metric transformation.` | a selection view |\n| a metric removed while a selection view names it | the selection view turns `INVALID` | put the metric back, or change the selection first (`\u002Fdenodo:views`, \"Before a column changes\") |\n\n## Reference\n\n- `references\u002Fmetric-views.md` — the full `CREATE METRIC VIEW` grammar, the join types\n  measured case by case, the `FILTER` clause, what a metric view refuses (cache, row\n  restrictions), how consumers read it, summaries, and the procedures that describe it.\n","# Metric views: KPIs defined once, sliced by any dimension\n\nA **metric view** (new in 9.5) declares a model instead of a query: one **fact view** that\nholds the measures, the **dimension views** around it, the **associations** that join them,\nthe **dimensions** a consumer may group and filter by, and the **metrics** — aggregations\nover the fact view. A query names the dimensions it wants, and Denodo builds the joins and\nthe aggregation for exactly those, so \"revenue\" is the same number in every tool at every\ngrain. BI tools, the Data Marketplace, Assisted Query and the MCP Server read it like any\nview, telling dimensions from metrics.\n\nThe model does not know this object, and most of what goes wrong with it returns a number,\nnot an error. Associations and the join rules for any two views are `\u002Fdenodo:views`; the\ndescriptions consumers read are `\u002Fdenodo:semantics`. Applying files is `\u002Fdenodo:execute`;\nthe working loop, the naming defaults and the safety rule are `\u002Fdenodo:vql`. A **mart** —\none aggregate of fixed grain for one consumer — is still a `CREATE VIEW … GROUP BY` in\n`\u002Fdenodo:views`; a metric view is for figures many consumers slice their own way.\n\n## The rule: a metric view stands alone in its `FROM`\n\nThe only thing built directly on a metric view is a **selection view** — the metric view\nalone in its `FROM`, dimensions in `SELECT` and `GROUP BY`, each metric as\n`evaluate_metric(\u003Cmetric>)`, a filter on dimensions in `WHERE`. Everything else — another\ndimension view, another fact, a second metric view, arithmetic over metrics, a total — is\ndone over selection views. Measured on 9.5.1, none of these says what is wrong: a join in the\nsame `FROM` runs until the query timeout; arithmetic or `ROUND` around `evaluate_metric`, or\n`SUM`\u002F`AVG` around a metric (`AVG(revenue)` returns the sum), is ignored, in a query and\ninside a `CREATE VIEW` alike; a ratio of two `evaluate_metric` returns no rows;\n`evaluate_metric` over another view is `NULL` (Silent failures, 1-3 and 10; Common mistakes,\nrow 1).\n\nOver a selection view — a view or a subquery — joins, `ROUND`, ratios and `CASE` work as in\nany view.\n\n## 1. The model, before the statement\n\n| Part | What it must be | Check |\n|---|---|---|\n| **Fact view** | the one view every metric reads — metrics over two views are refused | its grain: `SELECT COUNT(*), COUNT(DISTINCT \u003Ckey>) FROM \u003Cfact>` |\n| **Dimension views** | one row per key, and that key declared as the view's `PRIMARY KEY` — a duplicated key multiplies every metric under it; an undeclared one can empty every `HAVING` grouped by it over a file source (Silent failures, 12) | `SELECT COUNT(*), COUNT(DISTINCT \u003Ckey>) FROM \u003Cdimension>` — equal; `column_is_primary_key` in `GET_VIEW_COLUMNS()` |\n| **Associations** | one per dimension, from the fact (or from the dimension a snowflake hangs on), a tree with no second path | `SELECT association_name, mappings, valid FROM GET_ASSOCIATIONS() WHERE input_database_name = '\u003Cdb>' AND input_type = 'views' AND input_name = '\u003Cfact>'` |\n| **Fact rows without a dimension row** | decide: kept in a `NULL` group, or dropped | `SELECT COUNT(*), COUNT(\u003Cfk>) FROM \u003Cfact>`, and orphans: `… LEFT OUTER JOIN \u003Cdimension> … WHERE \u003Cfk> IS NOT NULL AND \u003Cdimension key> IS NULL` |\n\nThe checks in the table — *verified: 9.5.1 (live, 2026-10-05)*.\n\n**Which fact rows survive is decided by the association, not by the query** — *verified:\n9.5.1 (live, 2026-10-01)*:\n\n| In `ASSOCIATIONS` | Rows of the fact without a match | Dimension members without facts |\n|---|---|---|\n| `RIGHT`, dimension endpoint `(0,1)` or `PRINCIPAL (0,1)` | kept, in a `NULL` group of that dimension | absent |\n| `LEFT` | **dropped** | present, metrics `NULL` (`COUNT` 0) |\n| `INNER`, or `RIGHT` over a `(1)` endpoint | **dropped** | absent |\n| nothing | from the association: dimension endpoint `(1)` → as `INNER`; `PRINCIPAL (0,1)` → as `LEFT` | |\n\n- **The association template in `\u002Fdenodo:views` declares `(1)`**, under which `RIGHT` runs as\n  `INNER`: right when the check above finds no `NULL` key and no orphan, wrong otherwise — then\n  the association says `(0,1)`: `ENDPOINT \u003Crole> \u003Cfact view> (0,*)` and\n  `ENDPOINT \u003Crole> \u003Cdimension view> PRINCIPAL (0,1)`, the rest of the template unchanged.\n- The order of the two aliases does not matter; the type is read against the fact.\n- A query without any dimension of a group applies no join for it: the grand total always\n  counts every fact row, which is why a model that drops rows still shows the right total.\n- **Another association between the fact and the view under a dimension view** (the base\n  view a dimension `iv_` selects from) turned `RIGHT` into `INNER`; dropping it restores\n  `RIGHT`. Before you add an association near a metric view, run Verify.\n- An association between views you did not create is a proposal to their owner\n  (`\u002Fdenodo:semantics`). Over someone else's base views, build your own `iv_` views for the\n  fact and each dimension (`\u002Fdenodo:views`) — with the key declared, text trimmed — and put\n  the associations between those: everything the model needs is then yours, and no\n  association lands next to theirs. The metric view can also live in your own database and\n  name views of another (`\u003Cdb>.\u003Cview>` in `SOURCES`).\n\n## 2. Templates\n\nEach template is one file, applied whole. `CREATE OR REPLACE METRIC VIEW` re-applies safely;\nviews built on it stay valid while the metrics and dimensions they name stay.\n\n### The metric view\n\n```sql\n-- verified: 9.5.1 (live, 2026-10-07)\nCONNECT DATABASE sales_analytics;\n\nCREATE OR REPLACE METRIC VIEW household_metrics\n    FOLDER = '\u002F03 - business entities'\n    DESCRIPTION = 'Household KPIs by income band and buying potential. Query: SELECT \u003Cdimensions>, evaluate_metric(\u003Cmetric>) FROM household_metrics GROUP BY \u003Cdimensions>.'\n    (\n        buy_potential   ( description = 'Buying potential band of the household, as in the source (for example ''0-500'').' ),\n        household_count ( description = 'Number of households.' ),\n        avg_dependents  ( description = 'Average number of dependents per household.' )\n    )\n    SOURCES (\n        households AS bv_household_demographics,\n        bands      AS bv_income_band\n    )\n    ASSOCIATIONS (\n        households bands a_income_band_household RIGHT\n    )\n    DIMENSIONS (\n        income_band (\n            income_band_sk     AS bands.ib_income_band_sk,\n            income_lower_bound AS bands.ib_lower_bound,\n            income_upper_bound AS bands.ib_upper_bound\n        ),\n        household (\n            buy_potential AS TRIM(households.hd_buy_potential)\n        )\n    )\n    METRICS (\n        household_count AS COUNT(households.hd_demo_sk),\n        dependents      AS SUM(CAST('long', households.hd_dep_count)),\n        avg_dependents  AS AVG(households.hd_dep_count)\n    );\n```\n\nClause order: `FOLDER` → `DESCRIPTION` → `TAGS` → `( field properties )` → `SOURCES` →\n`ASSOCIATIONS` → `DIMENSIONS` → `METRICS`. Each alias in `SOURCES` is how dimensions and\nmetrics name that view. `a_income_band_household` is the association template of\n`\u002Fdenodo:views`; its `(1)` endpoint makes this `RIGHT` run as `INNER`, which is right here\nbecause every household has its band. `bv_income_band` declares its `PRIMARY KEY` in its own\nfile (Silent failures, 12).\n\n- **A dimension** is any expression over the sources without an aggregate: `TRIM`, `CASE`,\n  `COALESCE(\u003Cdim>.\u003Clabel>, '(not specified)')` to name the `NULL` group, `GETYEAR(\u003Cdate>)`, a\n  `CONCAT` over two views. Labels and buckets belong here, in the definition (Silent failures,\n  13). Groups (`income_band ( … )`) are how consumers see them; an attribute after the last\n  group, `, \u003Cname> AS \u003Cexpr>`, is a generic one.\n- **A metric** is an aggregate over fact columns: `SUM`, `COUNT(\u003Ccolumn>)`, `COUNT(DISTINCT\n  …)`, `AVG`, `MIN`, `MAX`, with `CASE` inside (`SUM(CASE WHEN … THEN amount ELSE 0 END)`) and\n  arithmetic over aggregates (`SUM(fee) \u002F NULLIF(SUM(amount), 0)`). It cannot name another\n  metric or use `COUNT(*)` — both are `invalid field: missing source schema for field:\n  \u003Cmetric>`; repeat the aggregates instead.\n- **The dialect rules hold inside a metric** (`\u002Fdenodo:vql`, **Expressions: VQL is not\n  PostgreSQL**): `SUM` over an `int` column stays `int`, hence `CAST('long', …)` above — never\n  over a `decimal`, which it truncates; an `int` divided by an `int` is an integer division\n  (Silent failures, 11). `ROUND` goes inside the metric (`ROUND(AVG(x), 2)`) or over a\n  selection view's column.\n- **`AVG(x)` and `COUNT(x)` skip `NULL`s**: say in the metric's description whether the KPI is\n  `AVG(x)` or `SUM(x) \u002F COUNT(\u003Ckey>)`, a missing value counted as zero.\n- **A filter that every metric obeys goes into the fact view** — an `iv_` derived view with\n  the `WHERE`, named in `SOURCES`. The `FILTER ( … )` clause the Design Studio wizard writes\n  after `SOURCES` fails on some queries with `Error applying metric transformation.` —\n  details in `references\u002Fmetric-views.md`.\n- Field descriptions go in the field properties above or by `ALTER VIEW \u003Cmetric view> (\n  ALTER COLUMN … ADD ( DESCRIPTION = '…' ) )`. A selection view inherits the description of\n  a dimension it passes through, never that of a metric: describe `evaluate_metric` columns\n  in the selection view (`\u002Fdenodo:semantics`).\n- `DESC VQL` returns `CREATE METRIC VIEW` without `OR REPLACE` and without `CONTEXT`: a\n  reference, not your file. A metric view takes no cache\n  (`Metric views do not support cache mode`): cache its sources (`\u002Fdenodo:cache`, **Templates**).\n\n### Selection views, a total and a share\n\n```sql\n-- verified: 9.5.1 (live, 2026-10-07)\nCONNECT DATABASE sales_analytics;\n\nCREATE OR REPLACE VIEW households_by_band\n    FOLDER = '\u002F03 - business entities'\n    DESCRIPTION = 'Households per income band, from the metrics of household_metrics. One row per income band.'\n    AS SELECT income_band_sk,\n              evaluate_metric(household_count) AS household_count,\n              evaluate_metric(avg_dependents)  AS avg_dependents\n       FROM household_metrics\n       GROUP BY income_band_sk\n    CONTEXT ('formatted' = 'yes');\n\nCREATE OR REPLACE VIEW households_total\n    FOLDER = '\u002F03 - business entities'\n    DESCRIPTION = 'All households, from the metrics of household_metrics. One row.'\n    AS SELECT evaluate_metric(household_count) AS household_count,\n              evaluate_metric(avg_dependents)  AS avg_dependents\n       FROM household_metrics\n    CONTEXT ('formatted' = 'yes');\n\nCREATE OR REPLACE VIEW household_share_by_band\n    FOLDER = '\u002F03 - business entities'\n    DESCRIPTION = 'Each income band''s share of all households, in percent. One row per income band.'\n    AS SELECT b.income_band_sk,\n              b.household_count,\n              ROUND(100.0 * b.household_count \u002F t.household_count, 2) AS household_share_pct,\n              ROUND(b.avg_dependents, 2)                              AS avg_dependents\n       FROM households_by_band b\n            CROSS JOIN households_total t\n    CONTEXT ('formatted' = 'yes');\n```\n\n- **A total comes from the metric, never from adding up rows.** `households_total` asks the\n  metric view with no dimension at all — allowed when the `SELECT` holds metrics only; a\n  literal next to them needs a `GROUP BY` (`For this type of query, the group by fields are\n  mandatory.`). Summing the rows of `households_by_band` is right for a sum or a count and\n  wrong for an average (an average of averages) and for a `COUNT(DISTINCT …)` (a customer in\n  two bands counts twice); for a coarser grain ask the metric view at that grain.\n- **A view or fact from outside the model** joins to a selection view grouped by the join\n  key — `GROUP BY \u003Ckey>` in the selection, then `JOIN \u003Cother view> ON s.\u003Ckey> = …`. **Two\n  metric views side by side**: one selection view each, at the same dimensions — aliased to\n  one name when the two metric views call it differently — joined on them; a `FULL OUTER\n  JOIN` keeps a member only one of them has, and the key column is then `COALESCE(a.\u003Ckey>,\n  b.\u003Ckey>)`.\n- Arithmetic over metrics — a ratio, a percent, rounding — is a column of the view over the\n  selections, as `household_share_pct` above.\n- **An average per member** — per customer, per store — is a selection grouped by the\n  member, averaged in the view above: `SELECT type, AVG(m.total) FROM (SELECT type,\n  customer_id, evaluate_metric(revenue) AS total … GROUP BY type, customer_id) m GROUP BY\n  type` — *verified: 9.5.1 (live, 2026-10-05)*. Its denominator is the members that have facts; members without any are absent\n  under `RIGHT`. When they must count as zero, take the members from a dimensions-only query\n  (it lists every one) and `LEFT OUTER JOIN` the totals to it.\n- **When nothing may be created** — a question to answer, not a view to build — the same\n  views are subqueries: `SELECT … FROM (\u003Cselection>) b CROSS JOIN (\u003Ctotal>) t`.\n\n### Periods: year over year, to date, running totals\n\nRead `references\u002Fperiods.md` before any year-over-year, to-date or running-total figure: its\ntemplates, and what decides them — a partial first or last period, which every total check\npasses; a coarser period, which adds up only sums and counts; \"today\", the server's clock and not\nthe data's; the year before, a join and not `LAG`.\n\n### Query it ad hoc\n\n```sql\n-- verified: 9.5.1 (live, 2026-10-07)\nSELECT income_band_sk, buy_potential,\n       evaluate_metric(household_count) AS household_count\n  FROM household_metrics\n WHERE buy_potential = '>10000'\n GROUP BY income_band_sk, buy_potential\nHAVING evaluate_metric(household_count) > 50\n ORDER BY household_count DESC;\n```\n\n| Works | Does not |\n|---|---|\n| `WHERE` on dimensions | `WHERE evaluate_metric(m) > …` — `Aggregate functions are not allowed here`; `WHERE \u003Cmetric> > …` — an `Error executing query` from the route |\n| `HAVING evaluate_metric(m) > …`, `HAVING \u003Cdimension> …` | `HAVING \u003Calias>` — `Field not found`; `HAVING \u003Cmetric>` — `is not a group by field` |\n| `ORDER BY \u003Calias>` or `ORDER BY evaluate_metric(m)` | `ORDER BY` a field not in the `SELECT` — `Field not found '\u003Cf>' in view with schema` |\n| an expression over a dimension in `SELECT`, grouped by the **raw dimension** — one row per raw value, every metric kept | the same expression grouped by it or by its **alias** (Silent failures, 13) |\n| `COUNT(*)` — the fact rows of the group | `GROUP BY \u003Cmetric>` — `The group by fields can only be dimensions or literals` |\n| `LIMIT` | no `GROUP BY` with a dimension in `SELECT`, or `SELECT *` — **0 rows, no error** |\n\nA query of dimensions only lists every member of the dimension view — every year of a\ncalendar — not the members that have facts. Ask with a metric to see what has data.\n\n## 3. Read what exists\n\n```sql\n-- verified: 9.5.1 (live, 2026-10-07)\nSELECT column_name, column_organization, column_dimension, column_definition, column_vdp_type\n  FROM GET_VIEW_COLUMNS()\n WHERE input_database_name = 'sales_analytics' AND input_view_name = 'household_metrics';\n\nSELECT name, sources, associations, filter\n  FROM GET_METRIC_VIEWS('sales_analytics', 'household_metrics');\n```\n\nThe first lists every dimension and metric with its expression — what a query may name;\nadd `column_remarks` for the descriptions.\nThe second gives the sources and, per association, the join type: `0` `INNER`, `1` `LEFT`,\n`2` `RIGHT`, `null` none written. `GET_METRIC_VIEWS` takes a `LIKE` pattern only as a\npositional argument; `WHERE input_name = '\u003Cpattern>'` returns nothing. These two are the way\nto read a metric view; `vql desc … --vql` of it, or of a view built on it, brings back its data\nsources and their encrypted passwords (`\u002Fdenodo:execute`, **Commands**).\n\n## Who applies what\n\nA new metric view and the views over it, created in this session, are yours to apply and\nverify. Changing a metric view you did not create in this session, even to add one metric:\nshow the file and get a yes (`\u002Fdenodo:vql`). A missing dimension or metric in another team's\nmetric view is a proposal to them; until they add it, your own metric view over the same\nsources answers the question without touching theirs.\n\n## Verify — the totals check\n\nThe one check that catches the silent failures of the model: the same figure three ways.\n\n```sql\n-- verified: 9.5.1 (live, 2026-10-07)\nCONNECT DATABASE sales_analytics;\n\n-- 1. The fact itself.\nSELECT COUNT(hd_demo_sk) AS fact_total FROM bv_household_demographics;\n\n-- 2. The metric with no dimension: no join is applied.\nSELECT evaluate_metric(household_count) AS metric_total FROM household_metrics;\n\n-- 3. Once per dimension group: its slices add up to the same total.\nSELECT SUM(s.household_count) AS slices_total, COUNT(*) AS slices\n  FROM (SELECT income_band_sk, evaluate_metric(household_count) AS household_count\n          FROM household_metrics\n         GROUP BY income_band_sk) s;\n```\n\n| You see | It means |\n|---|---|\n| 1 = 2 = 3 | every fact row is in every slicing |\n| 3 lower than 2 | fact rows dropped under that dimension: `NULL` or orphan keys with `INNER`, `LEFT`, no type, or a `(1)` endpoint (section 1) |\n| 3 higher than 2 | a key duplicated in that dimension view: those facts are counted twice |\n| 2 differs from 1 | the metric reads a different column or a `FILTER`; read its definition (section 3) |\n\n**On a metric view you did not build** the same three queries tell you what its\nassociations do with the rows: you cannot read the join from its definition alone.\n\nRun step 3 for one attribute of each dimension group — each association is applied only\nwhen a query names its dimensions. Use a sum or a count for it; then compare every other\nmetric with plain SQL over the fact and the dimension — one `FULL OUTER JOIN` of the\nselection and the plain `GROUP BY` on the dimensions, keeping the rows where any figure\ndiffers, checks every slice at once. Compare averages with a tolerance: the metric and\nhand-written SQL may type them differently and differ in the last digit. Also: `SELECT\nname, view_status FROM GET_VIEWS() WHERE input_database_name = '\u003Cdb>' AND\ninput_retrieve_invalid_views_only = true` is empty, and every selection view returns rows —\n*verified: 9.5.1 (live, 2026-10-05)*.\n\n## Silent failures\n\nEach runs without an error — *verified: 9.5.1 (live, 2026-10-01)*.\n\n| You wrote | What you get | Instead |\n|---|---|---|\n| 1. `AVG(\u003Csum metric>)`, `SUM(\u003Cavg metric>)`, `MAX(…)` | the metric's own aggregation | `evaluate_metric`; another aggregation is another metric, or a view over a selection |\n| 2. `evaluate_metric(a) * 100`, `ROUND(evaluate_metric(a), 2)` | `a`, unchanged | the expression over the selection view's column |\n| 3. `evaluate_metric(a) \u002F evaluate_metric(b)` | no rows | a ratio metric `SUM(x) \u002F NULLIF(SUM(y), 0)`, or the division over a selection |\n| 4. `SELECT * FROM \u003Cmetric view>`, a dimension without `GROUP BY` | no rows | dimensions in `GROUP BY`, metrics in `evaluate_metric` |\n| 5. `RIGHT` over an association with a `(1)` dimension endpoint | facts without a match dropped from every grouped query; the grand total still right | `(0,1)` on the association; the totals check |\n| 6. `LEFT`, or no type over a `PRINCIPAL (0,1)` endpoint | every dimension member, with empty metrics, and the unmatched facts dropped | `RIGHT` |\n| 7. a dimension view with a duplicated key | every metric of that key multiplied | one row per key in the dimension view |\n| 8. a metric without an aggregate (`n AS f.qty`) | accepted; every query with it returns no rows | an aggregate |\n| 9. the sum of a selection's rows as a total | an average of averages; distinct counts counted twice | the metric with no dimension |\n| 10. `evaluate_metric(x)` over a selection view | `NULL` | `x` — `evaluate_metric` belongs to the metric view only |\n| 11. a share as `SUM(CASE … 1 ELSE 0 END) \u002F COUNT(x)` | `0` — integer division | `1.0 * SUM(…) \u002F COUNT(x)` |\n| 12. `HAVING evaluate_metric(m) > …` grouped by the key of a dimension view that declares no `PRIMARY KEY`, over a file source | no rows: the plan is `INCOMPATIBLE_QUERY_VIEW`, `VOID PLAN` (the same query delegated to SQL Server answered) | declare the key in the dimension view's file; or filter the selection view's column in a view above |\n| 13. `COALESCE(\u003Cdim>, '…') AS d`, `UPPER(\u003Cdim>) AS d`, `GETYEAR(\u003Cdate>)` or a `CASE` in a query, grouped by it or by its alias, over sources delegated to a database (measured on SQL Server) | every `COUNT(DISTINCT …)` metric missing from the result — the plan already projects without it; sums, counts and averages stay (over file sources it stayed) — the expression grouped as itself: *verified: 9.5.1 (live, 2026-10-06)* | the expression in the dimension's definition, or `GROUP BY \u003Cdim>` and the label in the view above |\n| 14. a query over a selection view that reads none of its metric columns — `SELECT MIN(\u003Cdate>)`, a `COUNT(*)` | every member of the dimension view, with facts or without: a calendar's every day — *verified: 9.5.1 (live, 2026-10-06)* | read the metric in the same query: `WHERE \u003Cmetric column> > 0` |\n\n## Common mistakes\n\n| You wrote | Server says | Fix |\n|---|---|---|\n| a metric view joined to a view in one `FROM` | nothing, then `Error: Time out processing data` | a selection view, then the join |\n| `per AS total \u002F cnt` — a metric over metrics; `COUNT(*)` | `invalid field: missing source schema for field: per` | repeat the aggregates; `COUNT(\u003Cfact column>)` |\n| `MAX(…)` in a dimension | `The following fields cannot be projected: max(…) AS \u003Cdim>` | aggregates go in `METRICS` |\n| metrics over two source views | `There are metric fields referencing different views. Metrics must be built using fields from the same view.` | one fact view; the other figure is a second metric view |\n| two `SOURCES`, no `ASSOCIATIONS` | `invalid associations: It can't be empty` | name the association |\n| `evaluate_metric(\u003Cdimension>)` | `There are dimensions fields with function aggregation …` | dimensions go in `GROUP BY` |\n| a literal next to metrics, no `GROUP BY` | `For this type of query, the group by fields are mandatory.` | `GROUP BY '\u003Cliteral>'`, or the literal in the view above |\n| a view `AS SELECT * FROM \u003Cmetric view>` | created; its queries fail with `Error applying metric transformation.` | a selection view |\n| a metric removed while a selection view names it | the selection view turns `INVALID` | put the metric back, or change the selection first (`\u002Fdenodo:views`, \"Before a column changes\") |\n\n## Reference\n\n- `references\u002Fmetric-views.md` — the full `CREATE METRIC VIEW` grammar, the join types\n  measured case by case, the `FILTER` clause, what a metric view refuses (cache, row\n  restrictions), how consumers read it, summaries, and the procedures that describe it.\n",{},"main","fd41fb386b25141c459264d10a1c49bd3b4f4c4b","skills\u002Fmetrics","https:\u002F\u002Fgithub.com\u002Fkirivandubai\u002Fdenodo_wipe_coading_skills","https:\u002F\u002Fgithub.com\u002Fkirivandubai","2026-10-29 13:49:22",{"source":7,"skillName":5,"commands":29,"prompt":39,"zipAvailable":40},[30,33,36],{"tool":31,"command":32},"npx","npx skills add kirivandubai\u002Fdenodo_wipe_coading_skills --skill metrics",{"tool":34,"command":35},"bunx","bunx skills add kirivandubai\u002Fdenodo_wipe_coading_skills --skill metrics",{"tool":37,"command":38},"pnpm","pnpm dlx skills add kirivandubai\u002Fdenodo_wipe_coading_skills --skill metrics","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fkirivandubai\u002Fdenodo_wipe_coading_skills\u002Fblob\u002Fmain\u002Fskills\u002Fmetrics\u002FSKILL.md",true,[42,48],{"path":43,"resourceType":44,"resourceTypeDesc":45,"sizeBytes":46,"sourceHtmlUrl":47},"references\u002Fmetric-views.md","REFERENCE","参考文档",8800,"https:\u002F\u002Fgithub.com\u002Fkirivandubai\u002Fdenodo_wipe_coading_skills\u002Fblob\u002Fmain\u002Fskills\u002Fmetrics\u002Freferences\u002Fmetric-views.md",{"path":49,"resourceType":44,"resourceTypeDesc":45,"sizeBytes":50,"sourceHtmlUrl":51},"references\u002Fperiods.md",15560,"https:\u002F\u002Fgithub.com\u002Fkirivandubai\u002Fdenodo_wipe_coading_skills\u002Fblob\u002Fmain\u002Fskills\u002Fmetrics\u002Freferences\u002Fperiods.md",[53,60,67,74,81,88],{"skillNo":54,"name":55,"nameZh":6,"description":56,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":6,"repositoryNo":11,"starsCount":12,"licenseSpdxId":13,"licenseFileUrl":6,"sourceHtmlUrl":57,"categories":58,"keywords":59,"publishedAt":17},"01M7C2NGAJEKKG90BG9Q1Z2M96","vql","Use when doing anything with Denodo — creating or changing a database, folder, tag, data source, wrapper, base, derived, interface or metric view, association, cache, role or security policy, remote table or summary, a Data Marketplace object, a regression test or a Scheduler job; writing or fixing VQL, or an expression in a SELECT — text, dates, casts; deciding where an object lives and what to name it; \"where do I start\", \"build a mart\", \"connect a source\", \"vibe-code on Denodo\". Start here when the request is ambiguous or names no single object.","https:\u002F\u002Fgithub.com\u002Fkirivandubai\u002Fdenodo_wipe_coading_skills\u002Fblob\u002Fmain\u002Fskills\u002Fvql\u002FSKILL.md",[],[],{"skillNo":61,"name":62,"nameZh":6,"description":63,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":6,"repositoryNo":11,"starsCount":12,"licenseSpdxId":13,"licenseFileUrl":6,"sourceHtmlUrl":64,"categories":65,"keywords":66,"publishedAt":17},"01M7C2N5W4203SQ2TRYT6E0VSR","views","Use when building on top of views that already exist in Denodo 9.5 — a derived view (a join, an aggregate, a mart, a filtered or parameterised view, a UNION ALL of views of one entity, FLATTEN of a JSON array into rows, NEST of rows into an array), an interface view, or an association between two views. Also before changing a view others depend on — \"what uses this view\", \"can I drop this column\", \"where does this field come from\" — for whether a mart over a database runs there or pulls every row into Denodo, for \"combine these sources into one view\", \"expose this as a data product\", and for a view created without an error that fails on SELECT or turns up INVALID. Not for sources and base views — \u002Fdenodo:datasources; databases, folders, VDP tags — \u002Fdenodo:catalog; a view's cache — \u002Fdenodo:cache; a copy stored as a table — \u002Fdenodo:materialize; a metric view — \u002Fdenodo:metrics.","https:\u002F\u002Fgithub.com\u002Fkirivandubai\u002Fdenodo_wipe_coading_skills\u002Fblob\u002Fmain\u002Fskills\u002Fviews\u002FSKILL.md",[],[],{"skillNo":68,"name":69,"nameZh":6,"description":70,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":6,"repositoryNo":11,"starsCount":12,"licenseSpdxId":13,"licenseFileUrl":6,"sourceHtmlUrl":71,"categories":72,"keywords":73,"publishedAt":17},"01M7C2MY48XXHR87G39R0F0VTK","testing","Use when a Denodo 9.5 project needs regression tests, or its tests need running or fixing — .denodotest files that the Denodo Testing Tool runs in CI (%EXECUTION, %RESULTS[data|query|csv|exception], configuration.properties, denodo-test.sh) — \"add tests for this mart\", \"a safety net before I rewrite these views\", \"make sure the dashboards see no difference\", \"what should CI check on our data products\", \"prove the mart is right\", \"CI is red on the Denodo tests\", a test that fails on row order, on a column it did not ask for or on a value padded with spaces. Not for checking a new view once, right after creating it — \u002Fdenodo:views.","https:\u002F\u002Fgithub.com\u002Fkirivandubai\u002Fdenodo_wipe_coading_skills\u002Fblob\u002Fmain\u002Fskills\u002Ftesting\u002FSKILL.md",[],[],{"skillNo":75,"name":76,"nameZh":6,"description":77,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":6,"repositoryNo":11,"starsCount":12,"licenseSpdxId":13,"licenseFileUrl":6,"sourceHtmlUrl":78,"categories":79,"keywords":80,"publishedAt":17},"01M7C2MMFX40K0WTDNW05VCQEJ","semantics","Use when the views of an existing Denodo 9.5 database have to be understood by people and by AI consumers — the Denodo MCP Server, Assisted Query in the Data Marketplace, the AI SDK. Auditing a database for views and fields without descriptions, primary keys that are missing or not unique, missing associations and the VDP tag the MCP Server shows views by; writing view, field, association and tag descriptions from the data, and primary keys. Also for \"describe \u002F document these views\", \"prepare this database for AI, MCP or Assisted Query\", \"why does the agent not see this view through MCP\", a description that says the wrong thing. Not for building or changing what a view computes — \u002Fdenodo:views; not for marketplace-only metadata (logical names, property groups, marketplace tags) — \u002Fdenodo:marketplace.","https:\u002F\u002Fgithub.com\u002Fkirivandubai\u002Fdenodo_wipe_coading_skills\u002Fblob\u002Fmain\u002Fskills\u002Fsemantics\u002FSKILL.md",[],[],{"skillNo":82,"name":83,"nameZh":6,"description":84,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":6,"repositoryNo":11,"starsCount":12,"licenseSpdxId":13,"licenseFileUrl":6,"sourceHtmlUrl":85,"categories":86,"keywords":87,"publishedAt":17},"01M7C2M9XVTRVXYXMGTEC62EFB","security","Use when deciding who may read what in Denodo 9.5 — masking or hiding columns (PII, contact details, salaries, bank accounts) from some users or roles, letting a role see only some rows (a country, a tenant) or each person only their own, taking someone's access away (ALTER USER … REVOKE), denying a view to a group, a global security policy (CREATE GLOBAL_SECURITY_POLICY) and the VDP tags it reads, giving a user a role or giving a role access to a database or views (CREATE ROLE … GRANT EXECUTE, ALTER USER … GRANT ROLE), checking what a given user really gets back, or a user who sees data a restriction should hide (\"he still gets every row\", \"the masking stopped working\"). Not for a tag as a label without a policy (\u002Fdenodo:catalog) or for descriptions and the MCP visibility tag (\u002Fdenodo:semantics).","https:\u002F\u002Fgithub.com\u002Fkirivandubai\u002Fdenodo_wipe_coading_skills\u002Fblob\u002Fmain\u002Fskills\u002Fsecurity\u002FSKILL.md",[],[],{"skillNo":89,"name":90,"nameZh":6,"description":91,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":6,"repositoryNo":11,"starsCount":12,"licenseSpdxId":13,"licenseFileUrl":6,"sourceHtmlUrl":92,"categories":93,"keywords":94,"publishedAt":17},"01M7C2KYWB8B6ZC026CT3Z1MZA","scheduler","Use when something in Denodo 9.5 has to happen on a schedule, or a scheduled run misbehaves — refreshing the cache of a view every night or every hour, running REFRESH of a remote table or a procedure at a set time, exporting a view to a CSV file every week or month, running a Denodo Scheduler job now, stopping, pausing, enabling or deleting it, reading what last night's run did or why it failed, \"the cache doubles after every refresh\", \"the job fires at the wrong hour\", \"the export file is missing or has no header\". Denodo Scheduler projects, jobs, cron triggers and reports, through the REST API of the Scheduler administration tool. Not for switching a cache on or loading it once (\u002Fdenodo:cache), not for creating the remote table or summary itself (\u002Fdenodo:materialize).","https:\u002F\u002Fgithub.com\u002Fkirivandubai\u002Fdenodo_wipe_coading_skills\u002Fblob\u002Fmain\u002Fskills\u002Fscheduler\u002FSKILL.md",[],[]]