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

   
   ### Describe the bug
   
   When a table has a declared sort order and more than one file, applying 
`ORDER BY ... LIMIT n` to an `OFFSET` subquery returns fewer than `n` rows, and 
sometimes none at all.
   
   The physical plan applies the outer `LIMIT` first and the `OFFSET` after it:
   
   ```text
   GlobalLimitExec: skip=5, fetch=5
     SortPreservingMergeExec: [a@0 ASC NULLS LAST], fetch=10
       DataSourceExec: file_groups={2 groups: [p1.parquet], [p2.parquet]}, ..., 
output_ordering=[a@0 ASC NULLS LAST]
   ```
   
   The merge keeps only the first 10 rows, and the limit then skips 5 of those 
10, so 5 rows come back instead of 10.
   
   ### To Reproduce
   
   In `datafusion-cli`:
   
   ```sql
   COPY (SELECT value AS a FROM generate_series(1, 20) ORDER BY a) TO 
'/tmp/df_offset/p1.parquet';
   COPY (SELECT value AS a FROM generate_series(21, 40) ORDER BY a) TO 
'/tmp/df_offset/p2.parquet';
   CREATE EXTERNAL TABLE t (a BIGINT) STORED AS PARQUET LOCATION 
'/tmp/df_offset/' WITH ORDER (a ASC);
   
   SELECT count(*) FROM (SELECT * FROM (SELECT * FROM t OFFSET 5) ORDER BY a 
LIMIT 10) WHERE a > 0;
   -- 5, expected 10
   
   SELECT count(*) FROM (SELECT * FROM (SELECT * FROM t LIMIT 30 OFFSET 15) 
ORDER BY a LIMIT 10) WHERE a > 0;
   -- 0, expected 10
   
   EXPLAIN SELECT * FROM (SELECT * FROM t OFFSET 5) ORDER BY a LIMIT 10;
   ```
   
   (The `WHERE a > 0` keeps `count(*)` from being answered from statistics.)
   
   With `SET datafusion.execution.target_partitions = 1;` the same queries 
return 10 rows, and without the `OFFSET` the result is also correct.
   
   ### Expected behavior
   
   Skipping 5 of the 40 rows leaves 35, so `ORDER BY a LIMIT 10` should return 
10 rows. A correct plan would keep `skip + fetch` rows in the merge 
(`fetch=15`) before the limit skips 5, or not move the fetch below the `OFFSET` 
at all.
   
   ### Additional context
   
   `EXPLAIN VERBOSE` shows the change happens in `EnsureRequirements`:
   
   ```text
   -- before EnsureRequirements
   SortExec: TopK(fetch=10), expr=[a@0 ASC NULLS LAST], 
preserve_partitioning=[false]
     GlobalLimitExec: skip=5, fetch=None
       DataSourceExec: 2 file groups, output_ordering=[a@0 ASC NULLS LAST]
   
   -- after EnsureRequirements
   GlobalLimitExec: skip=5, fetch=None
     SortPreservingMergeExec: [a@0 ASC NULLS LAST], fetch=10
       DataSourceExec: 2 file groups, output_ordering=[a@0 ASC NULLS LAST]
   ```
   
   The fetch seems to be lost in `replace_with_order_preserving_variants`, in 
`datafusion/physical-optimizer/src/ensure_requirements/enforce_sorting/replace_with_order_preserving_variants.rs`:
   
   - `plan_with_order_preserving_variants` follows the chain of 
order-maintaining operators below the sort, which includes the 
`GlobalLimitExec` (around line 84), and turns the `CoalescePartitionsExec` 
under it into a `SortPreservingMergeExec` with the sort's fetch (around line 
150, called with `requirements.plan.fetch()` around line 271).
   - The new plan satisfies the ordering, so the `SortExec` and its fetch are 
dropped (around lines 274-291).
   
   Putting a fetch below an operator that drops rows (`skip > 0`) changes which 
rows survive. Other places already add `skip` to the fetch when they move it 
below a limit, for example `input_fetch` in `enforce_sorting/sort_pushdown.rs`.
   
   Related but different: #26488 (sort dropped below `OFFSET` when an aggregate 
reads the subquery) and #25065 (fetched merges removed during requirement 
enforcement).
   
   ### Willingness to contribute
   
   I cannot contribute a fix for this bug at this time
   


-- 
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