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
- Provide a non-empty SQL expression for the metric, e.g. 'SUM(sales)'.
- If the metric was meant to be a simple aggregation, switch expressionType to 'SIMPLE' with column+aggregate instead of an empty SQL expression.
- 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
- Require sqlExpression in form validation before a metric can be saved.
- Diff dashboard import/export payloads for metrics with empty sqlExpression.
- Never construct adhoc SQL metrics by hand without the expression string.
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
- Adhoc metric aggregate is invalid
- Adhoc metric expressionType is invalid
- Custom SQL fields cannot be parsed as a single SQL statement
- Custom SQL fields cannot contain set operations.
- Metric '%(metric)s' does not exist
AI-assisted analysis of apache/superset@f4587218dd (2026-08-14).
Data as JSON: /api/errors/85879a085ecc2a66.
Report an issue: GitHub.