Describe the bug
SELECT DISTINCT can return duplicate rows when its input has a functional dependency whose determinant columns cover all output columns, but whose uniqueness was lost earlier in the plan.
To Reproduce
Run the following in datafusion-cli:
CREATE TABLE users_with_pk (id INT, name VARCHAR, PRIMARY KEY (id)) AS VALUES
(1, 'alice'),
(2, 'bob');
CREATE TABLE user_orders (user_id INT, amount INT) AS VALUES
(1, 10),
(1, 20),
(2, 30);
SELECT DISTINCT u.id
FROM users_with_pk u
LEFT JOIN user_orders o ON u.id = o.user_id
ORDER BY u.id;
Actual output — id = 1 is repeated once per matching order:
+----+
| id |
+----+
| 1 |
| 1 |
| 2 |
+----+
EXPLAIN shows the DISTINCT has been dropped entirely — there is no Aggregate (or any other deduplication) in the plan:
logical_plan
01)Projection: u.id
02)--Left Join: u.id = o.user_id
03)----SubqueryAlias: u
04)------TableScan: users_with_pk projection=[id]
05)----SubqueryAlias: o
06)------TableScan: user_orders projection=[user_id]
Expected behavior
The query should return each u.id once:
+----+
| id |
+----+
| 1 |
| 2 |
+----+
and the plan should retain an Aggregate (or equivalent deduplication) for the DISTINCT.
Additional context
The faulty check is in datafusion/optimizer/src/replace_distinct_aggregate.rs (ReplaceDistinctWithAggregate::rewrite): it only verifies that dep.source_indices covers all fields, but a Dependency::Multi dependence merely means equal determinant keys imply equal rows — the key may still occur in multiple rows, so the input is not necessarily duplicate-free. The removal is only sound for Dependency::Single dependencies.
Fixed by #23548.
Describe the bug
SELECT DISTINCTcan return duplicate rows when its input has a functional dependency whose determinant columns cover all output columns, but whose uniqueness was lost earlier in the plan.To Reproduce
Run the following in
datafusion-cli:Actual output —
id = 1is repeated once per matching order:EXPLAINshows theDISTINCThas been dropped entirely — there is noAggregate(or any other deduplication) in the plan:Expected behavior
The query should return each
u.idonce:and the plan should retain an
Aggregate(or equivalent deduplication) for theDISTINCT.Additional context
The faulty check is in
datafusion/optimizer/src/replace_distinct_aggregate.rs(ReplaceDistinctWithAggregate::rewrite): it only verifies thatdep.source_indicescovers all fields, but aDependency::Multidependence merely means equal determinant keys imply equal rows — the key may still occur in multiple rows, so the input is not necessarily duplicate-free. The removal is only sound forDependency::Singledependencies.Fixed by #23548.