Repository: duckdb/duckdb (2026-06-27T08:39:33Z)

Issue:
Wrong results for LIST(DISTINCT ... ORDER BY ...) window aggregate
### What happens?

`LIST(DISTINCT c ORDER BY c)` returns the expected ascending list as a regular aggregate, but the same aggregate used as a window function can return the list in reverse order.

This reproduces on current main (584a1c9) and also v1.5.4.

### To Reproduce

To reproduce:
```sql
WITH src(c) AS (
  SELECT range % 2 FROM range(33)
)
SELECT
  LIST(DISTINCT c ORDER BY c) OVER (
    ORDER BY c
    ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
  ) AS window_list
FROM src
LIMIT 1;
```

Run:

```sh
./build/release/duckdb -json -f bug.sql
```

Actual:

```json
[{"window_list":[1, 0]}]
```

Expected: `ORDER BY c` should produce the ascending distinct list:

```json
[{"window_list":[0, 1]}]
```

Notes From Minimization:
* `DISTINCT` appears to be required; `LIST(c ORDER BY c)` behaves correctly.
* The window `ORDER BY` appears to be required; omitting it behaves correctly.
* The minimized pattern reproduces with 33 rows; the same pattern with 32 rows does not reproduce.
* `NULL` values are not required; two distinct non-NULL values are enough.
* A regular aggregate gives the expected order: `SELECT LIST(DISTINCT c ORDER BY c)` returns `[0, 1]`.
* The issue reproduces with `ROWS`, `RANGE`, and `GROUPS` full frames.
* Disabling `window_self_join` or `window_rewriter` did not change the result in local testing. Therefore, I think it's not a duplicate of  issue #23441.

### OS:

macOS 15.7.4, arm64

### DuckDB Version:

v1.5.4 and current main (584a1c9)

### DuckDB Client:

CLI

### Hardware:

_No response_

### Full Name:

Yibo Dong

### Affiliation:

National University of Singapore

### Did you include all relevant configuration (e.g., CPU architecture, Linux distribution) to reproduce the issue?

- [x] Yes, I have

### Did you include all code required to reproduce the issue?

- [x] Yes, I have

### Did you include all relevant data sets for reproducing the issue?

Yes


Discussion:
The bug appears to come from the window aggregate simplification path, not from the `LIST` aggregate implementation itself.

In `SimplifyWindowedAggregate`, DuckDB removes an aggregate-local `ORDER BY` when it is a prefix of the window `ORDER BY`:

```cpp
if (BoundWindowExpression::GetSharedOrders(wexpr.OrderBy(), arg_orders) == arg_orders.size()) {
  arg_orders.clear();
}
```

For this reproducer, `LIST(DISTINCT c ORDER BY c) OVER (ORDER BY c ...)` therefore has its aggregate-local `ORDER BY c` cleared.

Forcing the naive executor confirms this is an optimized execution path issue:

```sql
SET debug_window_mode='separate';
WITH src(c) AS (
  SELECT range % 2 FROM range(33)
)
SELECT
  LIST(DISTINCT c ORDER BY c) OVER (
    ORDER BY c
    ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
  ) AS window_list
FROM src
LIMIT 1;
```

This returns the expected `[0, 1]`.
I have identified the optimization bypass for DISTINCT expressions in SimplifyWindowedAggregate. I'm working on a PR for this.
@mohsinsurani  Thanks for checking this!



Patch:
diff --git a/src/function/window/window_aggregate_function.cpp b/src/function/window/window_aggregate_function.cpp
index 735c342e9ebd..c23bd2949f44 100644
--- a/src/function/window/window_aggregate_function.cpp
+++ b/src/function/window/window_aggregate_function.cpp
@@ -39,7 +39,7 @@ static BoundWindowExpression &SimplifyWindowedAggregate(BoundWindowExpression &w
 		}
 		if (aggr->GetOrderDependent() != AggregateOrderDependent::ORDER_DEPENDENT) {
 			arg_orders.clear();
-		} else {
+		} else if (!wexpr.Distinct()) {
 			//	If the argument order is prefix of the partition ordering,
 			//	then we can just use the partition ordering.
 			if (BoundWindowExpression::GetSharedOrders(wexpr.OrderBy(), arg_orders) == arg_orders.size()) {


Test patch:
diff --git a/test/sql/window/test_window_distinct.test b/test/sql/window/test_window_distinct.test
index 3d317d8c9365..719a5a27542c 100644
--- a/test/sql/window/test_window_distinct.test
+++ b/test/sql/window/test_window_distinct.test
@@ -7,6 +7,22 @@ SELECT COUNT(DISTINCT 42) OVER ()
 ----
 1
 
+# DISTINCT with an aggregate-local ORDER BY keeps that ordering even when it
+# matches the window ORDER BY.
+query I
+WITH src(c) AS (
+	SELECT range % 2
+	FROM range(33)
+)
+SELECT LIST(DISTINCT c ORDER BY c) OVER (
+	ORDER BY c
+	ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
+) AS window_list
+FROM src
+LIMIT 1
+----
+[0, 1]
+
 query IIII
 WITH t AS (
 	SELECT col0 AS a, col1 AS b