apache/superset · error · QueryObjectValidationError

Adhoc metric SQL expression is invalid

Error message

Adhoc metric SQL expression is invalid

What it means

QueryObjectValidationError raised in SqlaTable.adhoc_metric_to_sqla (models.py:1913) when an adhoc metric with expressionType 'SQL' has a sqlExpression that is not a non-empty string after strip. The SQL expression is the entire definition of such a metric, so blank/missing/whitespace-only values are rejected before Jinja processing and security validation.

Source

Thrown at superset/connectors/sqla/models.py:1913

            if (
                not isinstance(aggregate, str)
                or aggregate not in self.sqla_aggregations
            ):
                raise QueryObjectValidationError(_("Adhoc metric aggregate is invalid"))
            metric_column = metric.get("column") or {}
            column_name = cast(str, metric_column.get("column_name"))
            table_column: TableColumn | None = columns_by_name.get(column_name)
            if table_column:
                sqla_column = table_column.get_sqla_col(
                    template_processor=template_processor
                )
            else:
                sqla_column = column(column_name)
            sqla_metric = self.sqla_aggregations[aggregate](sqla_column)
        elif expression_type == utils.AdhocMetricExpressionType.SQL:
            expression: str | None = metric.get("sqlExpression")
            if not isinstance(expression, str) or not expression.strip():
                raise QueryObjectValidationError(
                    _("Adhoc metric SQL expression is invalid")
                )

            if not processed:
                try:
                    expression = self._process_select_expression(
                        expression=expression,
                        database_id=self.database_id,
                        engine=self.database.backend,
                        schema=self.schema,
                        template_processor=template_processor,
                    )
                except SupersetSecurityException as ex:
                    raise QueryObjectValidationError(ex.message) from ex

            sqla_metric = literal_column(expression)
        else:
            raise QueryObjectValidationError("Adhoc metric expressionType is invalid")

View on GitHub (pinned to f4587218dd)

Solutions

  1. Provide a non-empty SQL expression for the metric, e.g. 'SUM(sales)'.
  2. If the metric was meant to be a simple aggregation, switch expressionType to 'SIMPLE' with column+aggregate instead of an empty SQL expression.
  3. Re-open the chart in Explore and re-enter the custom SQL, then save to regenerate clean JSON.

Example fix

// before
{ expressionType: 'SQL', sqlExpression: '   ' }

// after
{ expressionType: 'SQL', sqlExpression: 'SUM(sales)' }
Defensive patterns

Strategy: type-guard

Validate before calling

def validate_sql_metric(metric: dict) -> None:
    if metric.get("expressionType") == "SQL":
        expr = metric.get("sqlExpression")
        if not isinstance(expr, str) or not expr.strip():
            raise ValueError("expressionType 'SQL' requires a non-empty sqlExpression")

Type guard

def is_valid_sql_metric(metric: dict) -> bool:
    return (
        metric.get("expressionType") == "SQL"
        and isinstance(metric.get("sqlExpression"), str)
        and bool(metric["sqlExpression"].strip())
    )

Try / catch

from superset.exceptions import QueryObjectValidationError

try:
    col = table.adhoc_metric_to_sqla(metric, columns_by_name)
except QueryObjectValidationError as ex:
    if "SQL expression is invalid" in str(ex):
        raise ValueError("Metric is missing its custom SQL; re-add it in Explore") from ex
    raise

Prevention

When it happens

Trigger: Chart payload with {expressionType:'SQL', sqlExpression:''} or sqlExpression: null/undefined, or a string of only spaces. Typical when the metric JSON was constructed programmatically or the UI saved an unfinished custom-SQL metric.

Common situations: Programmatic chart creation copying a template metric without filling sqlExpression; frontend state desync leaving the custom SQL box empty; migration/import of dashboards losing the expression field.

Related errors


AI-assisted analysis of apache/superset@f4587218dd (2026-08-14). Data as JSON: /api/errors/85879a085ecc2a66. Report an issue: GitHub.