Skip to content

Wrong results: a correlated filter under a window function is pulled above the window #25792

Description

@adriangb

Describe the bug

A correlated filter that sits below a window function inside a subquery is pulled out of the subquery and attached to the decorrelated join. The window function then runs over all rows of the inner table instead of only the rows that match the outer row, so row_number(), rank(), lag() and similar functions give different values and the query gives wrong results.

This affects EXISTS and LATERAL subqueries.

To Reproduce

CREATE TABLE o(k INT) AS VALUES (1), (2), (5);
CREATE TABLE b(id INT, y INT) AS VALUES (1, 1), (2, 2);

For each row of o, the subquery keeps only the rows of b where b.y = o.k, then numbers them. Each k matches at most one row of b, so that row always gets rn = 1.

EXISTS form

SELECT o.k FROM o
WHERE EXISTS (
  SELECT 1
  FROM (SELECT b.id, row_number() OVER (ORDER BY b.id) AS rn
        FROM (SELECT * FROM b WHERE b.y = o.k) AS b) AS w
  WHERE w.rn = 1)
ORDER BY o.k;
DataFusion DuckDB 1.5.2 PostgreSQL 17.11
1 1, 2 1, 2

LATERAL form

SELECT o.k, w.id, w.rn
FROM o, LATERAL (
  SELECT b.id, row_number() OVER (ORDER BY b.id) AS rn
  FROM (SELECT * FROM b WHERE b.y = o.k) AS b) AS w
ORDER BY o.k;
k id DataFusion rn DuckDB 1.5.2 rn PostgreSQL 17.11 rn
1 1 1 1 1
2 2 2 1 1

Expected behavior

The results of DuckDB and PostgreSQL above.

Additional context

The plan shows the cause. Filter: b.y = o.k is gone from below the WindowAggr and is now the condition of the semi join, so row_number() numbers both rows of b:

LeftSemi Join: o.k = __correlated_sq_1.y
  TableScan: o projection=[k]
  SubqueryAlias: __correlated_sq_1
    SubqueryAlias: w
      Projection: b.y
        Filter: row_number() ORDER BY [b.id ASC NULLS LAST] ... = UInt64(1)
          Projection: b.y, row_number() ORDER BY [b.id ASC NULLS LAST] ...
            WindowAggr: windowExpr=[[row_number() ORDER BY [b.id ASC NULLS LAST] ...]]
              SubqueryAlias: b
                TableScan: b projection=[id, y]

PullUpCorrelatedExpr in datafusion/optimizer/src/decorrelate.rs pulls a correlated Filter up through every node that it does not model. LogicalPlan::Window is one of those nodes. A filter can only move above a window if it reads only the PARTITION BY columns, which is not the case here (the window has no PARTITION BY).

Found on main at 6a792c6.

This is the same class of bug as these, each for a different node:

A fix can do the same as those PRs: mark the subquery as not pull-up-able when a Window has outer references below it, so the query fails with a not-implemented error instead of returning wrong results.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions