{"record":{"id":"fda55e8584b57756","repo":"apache/superset","slug":"the-following-entries-in-series-columns-are-miss","errorCode":null,"errorMessage":"The following entries in `series_columns` are missing in `columns`: %(columns)s. ","messagePattern":"The following entries in `series_columns` are missing in `columns`: (.+?)\\. ","errorType":"validation","errorClass":"QueryObjectValidationError","httpStatus":400,"severity":"error","filePath":"superset/common/query_object.py","lineNumber":386,"sourceCode":"                        # source_engine=engine ensures idempotency: this\n                        # method can run more than once (validate() is called\n                        # from both raise_for_access and get_df_payload), so\n                        # the second pass must be able to re-parse the\n                        # dialect-specific output (e.g. BigQuery backticks)\n                        # produced by the first pass.\n                        clause = transpile_to_dialect(\n                            clause, engine, source_engine=engine, identify=True\n                        )\n\n                    sanitized_clause = sanitize_clause(clause, engine)\n                    self.extras[param] = sanitized_clause\n                except QueryClauseValidationException as ex:\n                    raise QueryObjectValidationError(ex.message) from ex\n\n    def _validate_there_are_no_missing_series(self) -> None:\n        missing_series = [col for col in self.series_columns if col not in self.columns]\n        if missing_series:\n            raise QueryObjectValidationError(\n                _(\n                    \"The following entries in `series_columns` are missing \"\n                    \"in `columns`: %(columns)s. \",\n                    columns=\", \".join(f'\"{x}\"' for x in missing_series),\n                )\n            )\n\n    def to_dict(self) -> QueryObjectDict:\n        query_object_dict: QueryObjectDict = {\n            \"apply_fetch_values_predicate\": self.apply_fetch_values_predicate,\n            \"columns\": self.columns,\n            \"extras\": self.extras,\n            \"filter\": self.filter,\n            \"from_dttm\": self.from_dttm,\n            \"granularity\": self.granularity,\n            \"inner_from_dttm\": self.inner_from_dttm,\n            \"inner_to_dttm\": self.inner_to_dttm,\n            \"is_rowcount\": self.is_rowcount,","sourceCodeStart":368,"sourceCodeEnd":404,"githubUrl":"https://github.com/apache/superset/blob/f4587218dd19d046c3e4d00063e7d27f8a2ed354/superset/common/query_object.py#L368-L404","documentation":"QueryObjectValidationError raised by QueryObject._validate_there_are_no_missing_series when one or more names listed in series_columns do not appear in the query object's columns list. Superset requires every series (groupby/dimension used for series limiting or sequencing) to also be part of the selected columns so the generated SQL can actually project them. It is a client-side consistency check that fires before any SQL is issued.","triggerScenarios":"Building a QueryContext/QueryObject payload (e.g. via chart REST API /api/v1/chart/data or programmatically via superset.common.QueryObject) where series_columns contains a column name that is absent from columns. Typical with custom chart plugins or hand-crafted FormData that set series_columns independently of groupby/columns.","commonSituations":"Custom viz plugins that add series_columns without mirroring them into groupby; renames of a dataset column where the chart definition still stores the old name in series metadata; programmatic query generation that derives series_columns from a different source than columns.","solutions":["Inspect the query payload and add every missing series_columns entry to the columns (groupby) list of the same query object.","If the series column no longer exists in the dataset, remove or rename it in series_columns to match the current dataset schema.","For chart plugins, ensure the transformFormData logic copies the same field into both groupby and series_columns.","Run the query through /api/v1/chart/data with a minimal payload after fixing to confirm validation passes."],"exampleFix":"// before\nconst queryObject = {\n  columns: ['country'],\n  series_columns: ['country', 'region'], // 'region' missing from columns\n  metrics: ['sum__sales'],\n};\n\n// after\nconst queryObject = {\n  columns: ['country', 'region'], // every series column is projected\n  series_columns: ['country', 'region'],\n  metrics: ['sum__sales'],\n};","handlingStrategy":"validation","validationCode":"def validate_series_in_columns(query_object: dict) -> None:\n    columns = set(query_object.get(\"columns\") or [])\n    missing = [c for c in (query_object.get(\"series_columns\") or []) if c not in columns]\n    if missing:\n        raise ValueError(f\"series_columns not in columns: {missing}\")","typeGuard":null,"tryCatchPattern":"from superset.common.query_object import QueryObject\nfrom superset.exceptions import QueryObjectValidationError\n\ntry:\n    qo = QueryObject(**params)\nexcept QueryObjectValidationError as ex:\n    if \"series_columns\" in str(ex):\n        params[\"columns\"] = list(set(params.get(\"columns\", [])) | set(params.get(\"series_columns\", [])))\n        qo = QueryObject(**params)\n    else:\n        raise","preventionTips":["Derive series_columns from the same source array as columns/groupby in chart plugins.","Add a unit test asserting set(series_columns) <= set(columns) for generated query payloads.","After renaming dataset columns, run a dashboard chart smoke test to catch stale references."],"tags":["validation","query-object","superset","charts"],"backgroundTag":null,"analyzedSha":"f4587218dd19d046c3e4d00063e7d27f8a2ed354","analyzedAt":"2026-08-14T22:39:27.425Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}