{"record":{"id":"e1c0a35d83cec559","repo":"apache/superset","slug":"adhoc-metric-aggregate-is-invalid","errorCode":null,"errorMessage":"Adhoc metric aggregate is invalid","messagePattern":"Adhoc metric aggregate is invalid","errorType":"validation","errorClass":"QueryObjectValidationError","httpStatus":400,"severity":"error","filePath":"superset/connectors/sqla/models.py","lineNumber":1899,"sourceCode":"        Turn an adhoc metric into a sqlalchemy column.\n\n        :param dict metric: Adhoc metric definition\n        :param dict columns_by_name: Columns for the current table\n        :param template_processor: template_processor instance\n        :param bool processed: Whether the sqlExpression has already been processed\n        :returns: The metric defined as a sqlalchemy column\n        :rtype: sqlalchemy.sql.column\n        \"\"\"\n        expression_type = metric.get(\"expressionType\")\n        label = utils.get_metric_name(metric, self.verbose_map)\n\n        if expression_type == utils.AdhocMetricExpressionType.SIMPLE:\n            aggregate: Any = metric.get(\"aggregate\")\n            if (\n                not isinstance(aggregate, str)\n                or aggregate not in self.sqla_aggregations\n            ):\n                raise QueryObjectValidationError(_(\"Adhoc metric aggregate is invalid\"))\n            metric_column = metric.get(\"column\") or {}\n            column_name = cast(str, metric_column.get(\"column_name\"))\n            table_column: TableColumn | None = columns_by_name.get(column_name)\n            if table_column:\n                sqla_column = table_column.get_sqla_col(\n                    template_processor=template_processor\n                )\n            else:\n                sqla_column = column(column_name)\n            sqla_metric = self.sqla_aggregations[aggregate](sqla_column)\n        elif expression_type == utils.AdhocMetricExpressionType.SQL:\n            expression: str | None = metric.get(\"sqlExpression\")\n            if not isinstance(expression, str) or not expression.strip():\n                raise QueryObjectValidationError(\n                    _(\"Adhoc metric SQL expression is invalid\")\n                )\n\n            if not processed:","sourceCodeStart":1881,"sourceCodeEnd":1917,"githubUrl":"https://github.com/apache/superset/blob/f4587218dd19d046c3e4d00063e7d27f8a2ed354/superset/connectors/sqla/models.py#L1881-L1917","documentation":"QueryObjectValidationError raised in SqlaTable.adhoc_metric_to_sqla (models.py:1899) when an adhoc metric with expressionType 'SIMPLE' has an aggregate that is either not a string or not one of the allowed aggregations in self.sqla_aggregations (SUM, COUNT, COUNT_DISTINCT, AVG, MIN, MAX, and engine-specific additions). The aggregate name is used as a dict key to build the SQLAlchemy function, so unknown values are rejected.","triggerScenarios":"Chart payload with an adhoc metric like {expressionType:'SIMPLE', column:{...}, aggregate:'MEDIAN'} on an engine/datasource whose sqla_aggregations lacks MEDIAN; or aggregate missing/None/numeric. Happens with hand-built FormData, stale chart JSON after an aggregation was removed, or engine specs not registering the aggregate.","commonSituations":"Pasting example payloads that use aggregates unavailable for the database backend; charts authored against one DB type (e.g. Postgres with PERCENTILE via custom) migrated to another; frontend regression dropping the aggregate field.","solutions":["Use one of the aggregates supported by the datasource: check the datasource's aggregate options in Explore (they come from sqla_aggregations) — commonly SUM, AVG, COUNT, COUNT_DISTINCT, MIN, MAX.","If you need a custom aggregation (e.g. MEDIAN), switch the metric to expressionType 'SQL' and write the engine function directly: PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY x).","Ensure the adhoc metric dict includes a string aggregate field; re-pick the aggregate in the Explore UI to regenerate valid JSON."],"exampleFix":"// before\n{ expressionType: 'SIMPLE', column: { column_name: 'sales' }, aggregate: 'MEDIAN' }\n\n// after\n{ expressionType: 'SQL', sqlExpression: 'PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY sales)' }","handlingStrategy":"type-guard","validationCode":"ALLOWED_AGGREGATES = {\"SUM\", \"AVG\", \"COUNT\", \"COUNT_DISTINCT\", \"MIN\", \"MAX\"}\n\ndef validate_simple_metric(metric: dict) -> None:\n    if metric.get(\"expressionType\") == \"SIMPLE\":\n        agg = metric.get(\"aggregate\")\n        if not isinstance(agg, str) or agg not in ALLOWED_AGGREGATES:\n            raise ValueError(f\"unsupported aggregate {agg!r}; use one of {sorted(ALLOWED_AGGREGATES)} or expressionType 'SQL'\")","typeGuard":"def is_valid_simple_metric(metric: dict) -> bool:\n    return (\n        metric.get(\"expressionType\") == \"SIMPLE\"\n        and isinstance(metric.get(\"aggregate\"), str)\n        and metric[\"aggregate\"] in {\"SUM\", \"AVG\", \"COUNT\", \"COUNT_DISTINCT\", \"MIN\", \"MAX\"}\n    )","tryCatchPattern":"from superset.exceptions import QueryObjectValidationError\n\ntry:\n    col = table.adhoc_metric_to_sqla(metric, columns_by_name)\nexcept QueryObjectValidationError as ex:\n    if \"aggregate is invalid\" in str(ex):\n        metric = {**metric, \"expressionType\": \"SQL\", \"sqlExpression\": f\"{metric.get('aggregate')}({metric['column']['column_name']})\"}\n        metric.pop(\"aggregate\", None)\n        col = table.adhoc_metric_to_sqla(metric, columns_by_name)\n    else:\n        raise","preventionTips":["Treat aggregate as an enum sourced from the datasource's aggregate_options.","For exotic functions, use expressionType 'SQL' from the start.","Validate metric payloads against the datasource schema before sending chart data requests."],"tags":["adhoc","metric","aggregate","validation"],"backgroundTag":null,"analyzedSha":"f4587218dd19d046c3e4d00063e7d27f8a2ed354","analyzedAt":"2026-08-14T22:39:27.425Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}