timsaucer opened a new issue, #24885:
URL: https://github.com/apache/datafusion/issues/24885

   ### Is your feature request related to a problem or challenge?
   
   `FILTER (WHERE …)` is only accepted on *aggregate* window functions:
   
   ```
   Error during planning: FILTER clause can only be used with aggregate window 
functions.
   Found in 'last_value(d.v) FILTER (WHERE d.keep) ORDER BY [d.t ASC NULLS 
LAST] RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW'
   ```
   
   In SQL, `last_value(v) OVER (…)` always resolves to the `last_value` 
**window UDF** (`datafusion/functions-window/src/nth_value.rs`), so filtered 
first/last-value over a window is unreachable from SQL entirely. (PostgreSQL 
rejects `FILTER` on non-aggregate window functions too, so that part is not a 
deviation.)
   
   DataFusion does have a working alternative: the `last_value` **aggregate 
UDAF** can be used as a window function through the DataFrame API, and it 
honors `FILTER` with correct semantics:
   
   ```rust
   Expr::from(WindowFunction::new(last_value_udaf(), vec![col("v")]))
       .order_by(vec![col("t").sort(true, false)])
       .filter(col("keep"))
       .build()?
   ```
   
   ```
   +---+----+-------+----+
   | t | v  | keep  | w  |
   +---+----+-------+----+
   | 1 | 10 | true  | 10 |
   | 2 | 20 | false | 10 |
   | 3 |    | true  |    |   <- kept by filter, value is NULL -> result NULL 
(correct)
   | 4 | 40 | false |    |
   +---+----+-------+----+
   ```
   
   But that path is second-class today, and breaks as soon as the frame is 
anything but a simple ever-expanding one:
   
   ```
   This feature is not implemented: Aggregate can not be used as a sliding 
accumulator because
   `retract_batch` is not implemented: last_value(?table?.v) ORDER BY 
[?table?.t ASC NULLS LAST]
   ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
   ```
   
   The same error is reachable indirectly with a plain `UNBOUNDED PRECEDING .. 
CURRENT ROW` frame: when the physical optimizer reverses the window to avoid a 
sort, the reversed frame becomes `ROWS CURRENT ROW .. UNBOUNDED FOLLOWING`, 
which is not `is_ever_expanding()`, so `get_reverse_expr` builds a 
`SlidingAggregateWindowExpr` that then demands `retract_batch`. (The reversal 
path currently fails earlier with a separate renaming bug, #24884; with that 
fixed locally, the query proceeds to this error.)
   
   The usual `CASE` rewrite does **not** reproduce `FILTER` semantics when 
filtered-in values can be NULL:
   
   ```sql
   -- RESPECT NULLS: excluded rows become NULL and are then taken as the "last 
value"
   last_value(CASE WHEN keep THEN v END) OVER (ORDER BY t)
   -- t=2 (keep=false) -> NULL, expected 10
   
   -- IGNORE NULLS: conflates "excluded by filter" with "included but NULL"
   last_value(CASE WHEN keep THEN v END) IGNORE NULLS OVER (ORDER BY t)
   -- t=3 (keep=true, v IS NULL) -> 10, expected NULL
   ```
   
   So for filtered first/last-value over windows there is currently no correct 
formulation that also works with sliding frames.
   
   ### Describe the solution you'd like
   
   Implement `retract_batch` for the `first_value` / `last_value` aggregate 
accumulators (`datafusion/functions-aggregate/src/first_last.rs`) so they can 
be used as sliding window aggregates, the way `array_agg` was handled in #21957.
   
   Where a true retract is not possible in general, the alternative is to make 
the reversal/frame classification avoid `SlidingAggregateWindowExpr` when the 
reversed frame is plain in the reverse direction, so the common `UNBOUNDED 
PRECEDING .. CURRENT ROW` case does not silently degrade into a sliding 
accumulator.
   
   ### Describe alternatives you've considered
   
   - `CASE WHEN` rewrite with `IGNORE NULLS` / `RESPECT NULLS` — shown above, 
does not match `FILTER` semantics when filtered-in values may be NULL.
   - Allowing `FILTER` on the `first_value` / `last_value` window UDFs — would 
diverge from the SQL standard and PostgreSQL, and would duplicate filtering 
logic already present in the aggregate path.
   - Routing `last_value(...) FILTER (...) OVER (...)` in the SQL planner to 
the aggregate UDAF instead of the window UDF — plausible follow-up, but only 
useful once the aggregate path handles sliding/reversed frames, which is what 
this issue asks for.
   
   ### Additional context
   
   Related: #24884 (window reversal renames the output field for aggregate-UDAF 
window functions; blocks reaching this error via the reversal path).
   
   Reproduced on `main` (`d7b8e4fc1`).
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to