You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Metrics: an aggregate nested in another expression (e.g. APPROX_QUANTILES(x, 2)[OFFSET(1)]) loses its alias in the rewrite #6169
Version: sqlmesh 0.236.1 with sqlglot 30.8.0. The metric code in 0.236.4 is identical.
The metrics docs say the expression "can be any SQL statement that contains an aggregation function". When the aggregate is not the outermost node of the expression, the rewritten query references a column that doesn't exist.
ctx.rewrite("SELECT customer_id, METRIC(median_amount) FROM __semantic.__table GROUP BY customer_id").sql("bigquery")
total_amount is fine:
SELECT__table.customer_idAS customer_id, total_amount AS total_amount
FROM (SELECT customer_id, SUM(sushi__orders.amount) AS total_amount FROMsushi.ordersAS sushi__orders GROUP BY customer_id) AS __table
median_amount loses its alias in the inner select, so the outer median_amount is unresolved:
SELECT__table.customer_idAS customer_id, median_amount AS median_amount
FROM (SELECT customer_id, APPROX_QUANTILES(sushi__orders.amount, 2)[OFFSET(1)] FROMsushi.ordersAS sushi__orders GROUP BY customer_id) AS __table
Wrapping the aggregate (CAST(... AS FLOAT64), IFNULL(..., NULL), parentheses) gives the same output. The wrapper is dropped too.
Cause
Metric.aggs in sqlmesh/core/metric/definition.py keys each aggregate by agg.parent. That relies on the parent being the Alias that to_metric adds (exp.alias_(self.expression, self.name)). When the aggregate is nested (here under Bracket), the parent is the bracket, so the inner projection has no alias. formula then reduces to the bare metric name, so the wrapping expression is lost from the outer query as well.
Related
ARRAY_AGG(sushi.orders.amount ORDER BY sushi.orders.amount) fails with Could not infer a measures table. _get_measure_and_dim_tables.is_measure climbs If/Case/Binary/Paren/Distinct but not Order, so a column under an aggregate's ORDER BY is never taken as the measure.
Why it matters
On BigQuery there is no exact median aggregate (PERCENTILE_CONT is analytic only). So an exact median, or any order statistic, needs either an indexed APPROX_QUANTILES or a sorted ARRAY_AGG, and neither can be expressed as a metric today. A derived metric over a base aggregate (base[OFFSET(1)], where base is APPROX_QUANTILES(x, 2)) does rewrite correctly. So the expected behaviour would be for the nested case to be split the same way: alias the aggregate, and keep the wrapper in the formula.
Thanks for the detailed write-up and repro, it made this easy to track down.
ARRAY_AGG(... ORDER BY ...) / "Could not infer a measures table": this one is a small fix. When SQLMesh works out which column a metric measures, it doesn't look inside an aggregate's ORDER BY. We'll put up a PR for it. (edit, here it is: #6192)
Aggregate nested inside another expression (e.g. APPROX_QUANTILES(x, 2)[OFFSET(1)]): you're right about the cause. The rewriter assumes the aggregate is the outermost part of the expression. Fixing that properly means changing how every metric gets split into an aggregate part and a formula part, so it's a bigger change than it looks. We're not going to pick it up right away.
In the meantime, the derived-metric pattern you mentioned is the way to go:
Even if we don't support the nested form yet, it shouldn't quietly produce broken SQL. We'll look at raising a clear error when a metric's aggregate is nested inside another expression, and we'll update the docs so they don't say "any SQL statement".
If you'd like to take on the full nested-aggregate fix, PRs are welcome. The changes would be in Metric.aggs and Metric.formula in sqlmesh/core/metric/definition.py, and we're happy to review.
Activity
mday-io commentedon Nov 3, 2026
Thanks for the detailed write-up and repro, it made this easy to track down.
ARRAY_AGG(... ORDER BY ...)/ "Could not infer a measures table": this one is a small fix. When SQLMesh works out which column a metric measures, it doesn't look inside an aggregate'sORDER BY. We'll put up a PR for it. (edit, here it is: #6192)Aggregate nested inside another expression (e.g.
APPROX_QUANTILES(x, 2)[OFFSET(1)]): you're right about the cause. The rewriter assumes the aggregate is the outermost part of the expression. Fixing that properly means changing how every metric gets split into an aggregate part and a formula part, so it's a bigger change than it looks. We're not going to pick it up right away.In the meantime, the derived-metric pattern you mentioned is the way to go:
Even if we don't support the nested form yet, it shouldn't quietly produce broken SQL. We'll look at raising a clear error when a metric's aggregate is nested inside another expression, and we'll update the docs so they don't say "any SQL statement".
If you'd like to take on the full nested-aggregate fix, PRs are welcome. The changes would be in
Metric.aggsandMetric.formulainsqlmesh/core/metric/definition.py, and we're happy to review.