INTERNAL Error: "Failed to bind column reference" — window-over-aggregate used both standalone and inside arithmetic in a GROUP BY projection · Issue #23383 · duckdb/duckdb · GitHub
You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
INTERNAL Error: "Failed to bind column reference" — window-over-aggregate used both standalone and inside arithmetic in a GROUP BY projection #23383
In the projection of a GROUP BY query, using the same window-over-aggregate expression (MAX(SUM(n)) OVER (PARTITION BY name)) both standalone and as a sub-expression of arithmetic crashes the binder with an internal assertion instead of executing:
INTERNAL Error: Failed to bind column reference "" [19.1] (bindings: {#[22.0], #[22.1], #[20.0], #[19.0]})
... in duckdb::ColumnBindingResolver::VisitReplace ...
The same query with the window used only once, or used twice but never inside arithmetic, runs correctly — so it is specifically the duplicated window expression (one bare, one nested inside arithmetic) that fails to rebind.
To Reproduce
Failing query (CLI, no extensions, inline data):
SELECT
name,
MAX(SUM(n)) OVER (PARTITION BY name) AS w1,
SUM(n) /MAX(SUM(n)) OVER (PARTITION BY name) AS w2
FROM (VALUES ('a',1),('a',2),('b',3)) t(name, n)
GROUP BY name;
Output:
INTERNAL Error: Failed to bind column reference "" [19.1] (bindings: {#[22.0], #[22.1], #[20.0], #[19.0]})
This error signals an assertion failure within DuckDB. This usually occurs due to unexpected conditions or errors in the program's logic.
For more information, see https://duckdb.org/docs/stable/dev/internal_errors
-- (1) window used once, inside arithmetic only:SELECT name, SUM(n) /MAX(SUM(n)) OVER (PARTITION BY name) AS w2
FROM (VALUES ('a',1),('a',2),('b',3)) t(name,n)
GROUP BY name;
-- (2) window used twice but neither occurrence inside arithmetic:SELECT name,
MAX(SUM(n)) OVER (PARTITION BY name) AS w1,
MAX(SUM(n)) OVER (PARTITION BY name) AS w2
FROM (VALUES ('a',1),('a',2),('b',3)) t(name,n)
GROUP BY name;
The bug reproduces across versions: CLI v1.5.2 (Variegata) 8a5851971f, @duckdb/node-bindings 1.5.3-r.2, and @duckdb/node-api 1.5.4-r.1 (latest stable at time of filing). It is therefore not a recent regression.
(Found while reducing SQL generated by Malloy for an "ungrouped aggregate" / level-of-detail query, but the reproduction above is plain SQL with no Malloy or external dependency.)
COPY (SELECT'a'AS g) TO 'min.parquet' (FORMAT parquet);
SELECT NULLIF(SUM(1) OVER (PARTITION BY g), 0) AS pct
FROM (SELECT g FROM read_parquet('min.parquet') GROUP BY g) t;
Linux only. A bisection is running, wil report results here.
Linux results
In a fresh ubuntu:latest container on aarch64:
root@c9272dd9f87b:/# /root/.duckdb/cli/latest/duckdb
DuckDB v1.5.4 (Variegata)
Enter ".help" for usage hints.
memory D COPY (SELECT 'a' AS g) TO 'min.parquet' (FORMAT parquet);
memory D SELECT NULLIF(SUM(1) OVER (PARTITION BY g), 0) AS pct
FROM (SELECT g FROM read_parquet('min.parquet') GROUP BY g) t;
INTERNAL Error:
Failed to bind column reference "" [18.1] (bindings: {#[21.0], #[19.0], #[18.0]})
Stack Trace:
/root/.duckdb/cli/latest/duckdb() [0x6e9804]
/root/.duckdb/cli/latest/duckdb() [0x6e98c8]
/root/.duckdb/cli/latest/duckdb() [0x6ec95c]
/root/.duckdb/cli/latest/duckdb() [0x8c787c]
/root/.duckdb/cli/latest/duckdb() [0x8c05f0]
/root/.duckdb/cli/latest/duckdb() [0xea8bc0]
/root/.duckdb/cli/latest/duckdb() [0xea7f24]
/root/.duckdb/cli/latest/duckdb() [0xea5f20]
/root/.duckdb/cli/latest/duckdb() [0xea64a0]
/root/.duckdb/cli/latest/duckdb() [0x8b1528]
/root/.duckdb/cli/latest/duckdb() [0x8b204c]
/root/.duckdb/cli/latest/duckdb() [0x8b22b0]
/root/.duckdb/cli/latest/duckdb() [0x97f1d4]
/root/.duckdb/cli/latest/duckdb() [0x97fd8c]
/root/.duckdb/cli/latest/duckdb() [0x984510]
/root/.duckdb/cli/latest/duckdb() [0x98492c]
/root/.duckdb/cli/latest/duckdb() [0x984d40]
/root/.duckdb/cli/latest/duckdb() [0x985930]
/root/.duckdb/cli/latest/duckdb() [0x988304]
/root/.duckdb/cli/latest/duckdb() [0x988468]
/root/.duckdb/cli/latest/duckdb() [0x98856c]
/root/.duckdb/cli/latest/duckdb() [0x9893ac]
/root/.duckdb/cli/latest/duckdb() [0x470de0]
/root/.duckdb/cli/latest/duckdb() [0x471284]
/root/.duckdb/cli/latest/duckdb() [0x471704]
/root/.duckdb/cli/latest/duckdb() [0x47a654]
/root/.duckdb/cli/latest/duckdb() [0x47b920]
/root/.duckdb/cli/latest/duckdb() [0x45208c]
/usr/lib/aarch64-linux-gnu/libc.so.6(+0x22f1c) [0xffff9e3c2f1c]
/usr/lib/aarch64-linux-gnu/libc.so.6(__libc_start_main+0x9c) [0xffff9e3c305c]
/root/.duckdb/cli/latest/duckdb() [0x456710]
This error signals an assertion failure within DuckDB. This usually occurs due to unexpected conditions or errors in the program's logic.
For more information, see https://duckdb.org/docs/stable/dev/internal_errors
macOS results
➜ ~ duckdb
DuckDB v1.5.4 (Variegata)
Enter ".help" for usage hints.
memory D COPY (SELECT 'a' AS g) TO 'min.parquet' (FORMAT parquet);
memory D SELECT NULLIF(SUM(1) OVER (PARTITION BY g), 0) AS pct
FROM (SELECT g FROM read_parquet('min.parquet') GROUP BY g) t;
┌────────┐
│ pct │
│ int128 │
├────────┤
│ 1 │
└────────┘
Hi, I've been investigating this and found the root cause. I've opened a PR with a fix --> #23412 would love feedback.
What's happening: The binder creates one entry in node.windows per occurrence of a window expression no deduplication. So MAX(SUM(n)) OVER (PARTITION BY name) appearing twice (once as w1, once inside the division for w2) produces two separate entries in the LogicalWindow, at indices (window_index, 0) and (window_index, 1).
The window_self_join optimizer was then creating one aggregate expression per window expression (including those duplicates), producing two aggregate outputs: (aggregate_index, 0) and (aggregate_index, 1). It registered binding replacements for both. A later optimizer (column_lifetime) then correctly identified that (aggregate_index, 1) is a duplicate of (aggregate_index, 0) and pruned it but the replacement binding still pointed expressions at the now-missing (aggregate_index, 1), causing the crash in ColumnBindingResolver.
Fix I tried is: Deduplicate identical window expressions when building the new aggregate inside window_self_join. If two window expressions are equal (via BoundWindowExpression::Equals), they share the same aggregate output index. Both (window_index, 0) and (window_index, 1) then map to (aggregate_index, 0) one aggregate, nothing to prune, no crash..
Activity
krlmlr commentedon Jun 20, 2026
Similar reprex, not sure if it's the same issue:
Linux only. A bisection is running, wil report results here.
Linux results
In a fresh ubuntu:latest container on aarch64:
root@c9272dd9f87b:/# /root/.duckdb/cli/latest/duckdb DuckDB v1.5.4 (Variegata) Enter ".help" for usage hints. memory D COPY (SELECT 'a' AS g) TO 'min.parquet' (FORMAT parquet); memory D SELECT NULLIF(SUM(1) OVER (PARTITION BY g), 0) AS pct FROM (SELECT g FROM read_parquet('min.parquet') GROUP BY g) t; INTERNAL Error: Failed to bind column reference "" [18.1] (bindings: {#[21.0], #[19.0], #[18.0]}) Stack Trace: /root/.duckdb/cli/latest/duckdb() [0x6e9804] /root/.duckdb/cli/latest/duckdb() [0x6e98c8] /root/.duckdb/cli/latest/duckdb() [0x6ec95c] /root/.duckdb/cli/latest/duckdb() [0x8c787c] /root/.duckdb/cli/latest/duckdb() [0x8c05f0] /root/.duckdb/cli/latest/duckdb() [0xea8bc0] /root/.duckdb/cli/latest/duckdb() [0xea7f24] /root/.duckdb/cli/latest/duckdb() [0xea5f20] /root/.duckdb/cli/latest/duckdb() [0xea64a0] /root/.duckdb/cli/latest/duckdb() [0x8b1528] /root/.duckdb/cli/latest/duckdb() [0x8b204c] /root/.duckdb/cli/latest/duckdb() [0x8b22b0] /root/.duckdb/cli/latest/duckdb() [0x97f1d4] /root/.duckdb/cli/latest/duckdb() [0x97fd8c] /root/.duckdb/cli/latest/duckdb() [0x984510] /root/.duckdb/cli/latest/duckdb() [0x98492c] /root/.duckdb/cli/latest/duckdb() [0x984d40] /root/.duckdb/cli/latest/duckdb() [0x985930] /root/.duckdb/cli/latest/duckdb() [0x988304] /root/.duckdb/cli/latest/duckdb() [0x988468] /root/.duckdb/cli/latest/duckdb() [0x98856c] /root/.duckdb/cli/latest/duckdb() [0x9893ac] /root/.duckdb/cli/latest/duckdb() [0x470de0] /root/.duckdb/cli/latest/duckdb() [0x471284] /root/.duckdb/cli/latest/duckdb() [0x471704] /root/.duckdb/cli/latest/duckdb() [0x47a654] /root/.duckdb/cli/latest/duckdb() [0x47b920] /root/.duckdb/cli/latest/duckdb() [0x45208c] /usr/lib/aarch64-linux-gnu/libc.so.6(+0x22f1c) [0xffff9e3c2f1c] /usr/lib/aarch64-linux-gnu/libc.so.6(__libc_start_main+0x9c) [0xffff9e3c305c] /root/.duckdb/cli/latest/duckdb() [0x456710] This error signals an assertion failure within DuckDB. This usually occurs due to unexpected conditions or errors in the program's logic. For more information, see https://duckdb.org/docs/stable/dev/internal_errorsmacOS results
➜ ~ duckdb DuckDB v1.5.4 (Variegata) Enter ".help" for usage hints. memory D COPY (SELECT 'a' AS g) TO 'min.parquet' (FORMAT parquet); memory D SELECT NULLIF(SUM(1) OVER (PARTITION BY g), 0) AS pct FROM (SELECT g FROM read_parquet('min.parquet') GROUP BY g) t; ┌────────┐ │ pct │ │ int128 │ ├────────┤ │ 1 │ └────────┘krlmlr commentedon Jun 20, 2026
Bisection suggests that #20515 is the trigger.
krlmlr commentedon Jun 20, 2026
Confirming the two reprexes are the same problem.
hawkfish commentedon Jun 22, 2026
Workaround (as you might expect...):
Youssef-joe commentedon Jun 23, 2026
Hi, I've been investigating this and found the root cause. I've opened a PR with a fix --> #23412 would love feedback.
What's happening: The binder creates one entry in node.windows per occurrence of a window expression no deduplication. So MAX(SUM(n)) OVER (PARTITION BY name) appearing twice (once as w1, once inside the division for w2) produces two separate entries in the LogicalWindow, at indices (window_index, 0) and (window_index, 1).
The window_self_join optimizer was then creating one aggregate expression per window expression (including those duplicates), producing two aggregate outputs: (aggregate_index, 0) and (aggregate_index, 1). It registered binding replacements for both. A later optimizer (column_lifetime) then correctly identified that (aggregate_index, 1) is a duplicate of (aggregate_index, 0) and pruned it but the replacement binding still pointed expressions at the now-missing (aggregate_index, 1), causing the crash in ColumnBindingResolver.
Fix I tried is: Deduplicate identical window expressions when building the new aggregate inside window_self_join. If two window expressions are equal (via BoundWindowExpression::Equals), they share the same aggregate output index. Both (window_index, 0) and (window_index, 1) then map to (aggregate_index, 0) one aggregate, nothing to prune, no crash..
Issue #23383: Common Aggregate CTEs
Issue #23383: Common Aggregate CTEs (#23409)
lnkuiper commentedon Jun 23, 2026
Closed via #23409
Xfail test_complex on DuckDB 1.5.1-1.5.4 binder regression