[Bug] Table chart applies Time grain to only the first temporal column (regression from 4.0.2)
Maintainers usually reply within 2 days
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 72/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- react, sql, typescript
- Domain
- data-visualization, frontend
Research direction
The bug is in superset-frontend/plugins/plugin-chart-table/src/buildQuery.ts, where a temporalColumnAdded flag lets only the first temporal column receive the time grain; the same pattern exists in plugins/plugin-chart-ag-grid-table/src/buildQuery.ts. Start by reading that code path and reproducing the generated SQL with two temporal dimensions and Time grain set. Done when every physical temporal column used as a dimension gets the grain expression and the existing table-chart buildQuery tests still pass.
Written by the indexing model from the issue text.
Description
Bug description
When a Table chart has two or more temporal (date) dimensions, the "Time grain" setting is applied to only the first temporal column. The remaining temporal columns are sent to the database as raw columns, so rows that differ only by day/time no longer collapse into one row. This is a regression: the same chart works correctly on 4.0.2.
Example: with Dimensions StartDate, EndDate and Time grain Month, the chart returns three rows that all display as "Aug 2026 / Sep 2026" but have different counts (4, 1, 1), because EndDate is not grouped by month.
Steps to reproduce
- Use a dataset (or physical table) that has two temporal columns, e.g.
StartDateandEndDate. - Create a Table chart (
table_v2). - Set Query mode = Aggregate.
- Dimensions:
StartDate,EndDate. - Metric:
COUNT(DISTINCT Counts)(any aggregate metric). - Set Time grain =
Month. - Click "Update chart" and inspect the generated SQL ("View query").
Minimal form data:
{
"viz_type": "table",
"query_mode": "aggregate",
"granularity_sqla": "StartDate",
"time_grain_sqla": "P1M",
"columns": ["StartDate", "EndDate"],
"metrics": ["COUNT_DISTINCT(Counts)"],
"temporal_columns_lookup": { "StartDate": true, "EndDate": true }
}
Expected behavior
Time grain should be applied to every temporal column that is used as a dimension (the 4.0.2 behavior), i.e. both StartDate and EndDate should be truncated to month and grouped. Expected SQL:
SELECT
TO_DATE(DATE_SUB(`StartDate`, INTERVAL (DAYOFMONTH(`StartDate`) - 1) DAY)) AS `StartDate`,
TO_DATE(DATE_SUB(`EndDate`, INTERVAL (DAYOFMONTH(`EndDate`) - 1) DAY)) AS `EndDate`,
COUNT(DISTINCT `Counts`) AS `Counts`
FROM `activity`
WHERE ...
GROUP BY
TO_DATE(DATE_SUB(`StartDate`, INTERVAL (DAYOFMONTH(`StartDate`) - 1) DAY)),
TO_DATE(DATE_SUB(`EndDate`, INTERVAL (DAYOFMONTH(`EndDate`) - 1) DAY))
Result: one row — Aug 2026 | Sep 2026 | 6.
Actual behavior
Only the first temporal column is converted to a time-grain expression; the second one is passed through unchanged:
SELECT
TO_DATE(DATE_SUB(`StartDate`, INTERVAL (DAYOFMONTH(`StartDate`) - 1) DAY)) AS `StartDate`,
`EndDate` AS `EndDate`,
COUNT(DISTINCT `Counts`) AS `Counts`
FROM `activity`
WHERE
`StartDate` >= STR_TO_DATE('2026-08-01', '%Y-%m-%d')
AND `StartDate` < STR_TO_DATE('2026-08-31', '%Y-%m-%d')
AND `EndDate` >= STR_TO_DATE('2026-09-01', '%Y-%m-%d')
AND `EndDate` < STR_TO_DATE('2026-09-30', '%Y-%m-%d')
GROUP BY
TO_DATE(DATE_SUB(`StartDate`, INTERVAL (DAYOFMONTH(`StartDate`) - 1) DAY)),
`EndDate`
ORDER BY `Counts` DESC
LIMIT 10000
Result (duplicated dimension combinations, all displayed as the same month):
| StartDate | EndDate | Counts |
|---|---|---|
| Aug 2026 | Sep 2026 | 4 |
| Aug 2026 | Sep 2026 | 1 |
| Aug 2026 | Sep 2026 | 1 |
Because the raw EndDate values differ (different days) while the column is rendered as "Sep 2026", the rows look identical to the user but are not aggregated — the numbers silently look wrong.
Root cause
superset-frontend/plugins/plugin-chart-table/src/buildQuery.ts
4.0.2 maps over all columns and replaces each temporal column with an ad-hoc time-grain column (position preserved):
columns = columns.map(col => {
if (
isPhysicalColumn(col) &&
time_grain_sqla &&
formData?.temporal_columns_lookup?.[col]
) {
return {
timeGrain: time_grain_sqla,
columnType: 'BASE_AXIS',
sqlExpression: col,
label: col,
expressionType: 'SQL',
} as AdhocColumn;
}
return col;
});
6.1.0 uses a temporalColumnAdded flag, so only the first matching column is converted; all subsequent temporal columns fall through unchanged. It also removes that column and re-adds it at index 0:
let temporalColumnAdded = false;
let temporalColumn = null;
columns = columns.filter(col => {
const shouldBeAdded =
isPhysicalColumn(col) &&
time_grain_sqla &&
temporalColumnsLookup?.[col];
if (shouldBeAdded && !temporalColumnAdded) {
temporalColumn = {
timeGrain: time_grain_sqla,
columnType: 'BASE_AXIS',
sqlExpression: col,
label: col,
expressionType: 'SQL',
} as AdhocColumn;
temporalColumnAdded = true;
return false;
}
return true;
});
if (temporalColumn) {
columns = [temporalColumn, ...columns];
}
The same logic exists in plugins/plugin-chart-ag-grid-table/src/buildQuery.ts (experimental AG Grid Table), so that chart is affected too. Pivot Table does not contain this code path.
Suggested fix
Keep the "temporal column first" presentation behavior if it is intentional, but do not use a single temporalColumnAdded flag to decide whether to apply the grain — apply the grain to every physical temporal column, e.g.:
columns = columns.map(col =>
isPhysicalColumn(col) && time_grain_sqla && temporalColumnsLookup?.[col]
? {
timeGrain: time_grain_sqla,
columnType: 'BASE_AXIS',
sqlExpression: col,
label: col,
expressionType: 'SQL',
}
: col,
);
and reorder afterwards only if the "first temporal column first" behavior is still desired.
Workaround
- Use only one temporal dimension per chart, or
- Wrap the second date dimension as an ad-hoc "Custom SQL" column that truncates explicitly, e.g.
DATE_FORMAT(\EndDate`, '%Y-%m-01')` (database-specific), or - Pre-truncate the date in the dataset / SQL before charting.
Note: swapping the order of the two dimensions only moves the problem to the other column.
Screenshots/recordings
No response
Superset version
6.1.0
Python version
3.11
Node version
16
Browser
Chrome
Additional context
No response
Checklist
- I have searched Superset docs and Slack and didn't find a solution to my problem.
- I have searched the GitHub issue tracker and didn't find a similar bug report.
- I have checked Superset's logs for errors and if I found a relevant Python stacktrace, I included it here as text in the "additional context" section.
- Dominant language
- Python
- Stars
- 75.4k
- Forks
- 18.6k
- Avg merge
- 2d 14h
- Merged PRs (30d)
- 603
Getting set up
Starts the project's dev container in your browser, under your own GitHub account.
- Ships a Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from apache/superset
-
"Sync columns from source" fails on ClickHouse tables with ALIAS, MATERIALIZED or DEFAULT columnsPossibly taken @bmuraykhi claimed this 2 days ago. Open#bug 🦾 ai-candidate
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
Maintainers usually reply within 2 days
-
Chart and dashboard thumbnails ignore SUPERSET_APP_ROOTPossibly taken @greg-om claimed this 13 days ago. Open#bug 🦾 ai-candidate
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
apache/superset#45638 · 1 comment ·
Maintainers usually reply within 2 days
-
Bar chart: bars on a temporal x-axis shrink to hairlines when the time grain is finer than the data (7.0.0rc2)Possibly taken @Anikesh0001 claimed this 15 days ago. Open#bug 🦾 ai-candidate
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
apache/superset#45474 · 4 comments ·
Maintainers usually reply within 2 days
-
get_time_filter Jinja macro returns 500 when the chart has a custom SQL filterPossibly taken @SEPURI-SAI-KRISHNA claimed this 21 days ago. Open🦾 ai-candidate
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
apache/superset#45269 · 2 comments ·
Maintainers usually reply within 2 days
-
flatten post-processing drops index columns when the index has several levelsPossibly taken @AliIbnBotir claimed this 22 days ago. Open#bug 🦾 ai-candidate
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
apache/superset#45261 · 1 comment ·
Maintainers usually reply within 2 days
Similar issues
-
area:docs documentation effort:M ready-for-agent
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
mczielinski/ob-analytics#423 ·
Maintainers usually reply within 1 day
-
needs-triage
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
gke-labs/kube-agents#4838 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 83/100
danyuchn/asd-ste100-skill#60 ·
Maintainers usually reply within 1 day
-
Difficulty 1/5 Under an hour Newbie friendliness 64/100
Gathin23/cleanyourtv#3299 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
autowarefoundation/autoware_carla_scenario#1485 ·
Maintainers usually reply within 1 day