Skip to content

bug: HAVING (AGG(col) = AGG(col)) keeps groups whose aggregate is NULL — the same self-comparison in a projection column filters them correctly #20379

Description

@Manuel-Neuer1

Search before asking

  • I had searched in the issues and found no similar issues.

Version

8.0.90-v1.2.925-patch-10-1abb4b1e29 (rust nightly 2026-08-25) — the latest patch release as of 2026-08-25. Reproduced 3/3 runs.

What's Wrong?

A reflexive aggregate comparison MIN(col) = MIN(col) is NULL-typed under SQL three-valued logic: for a group whose aggregate result is NULL, the comparison evaluates to NULL and the group must be filtered out by HAVING.

Databend keeps the group when the comparison sits in the HAVING clause, and drops it correctly when the identical expression is placed in a projection column of a derived table and filtered afterwards:

Query form Groups returned (for the reproducer below) Verdict
HAVING (MIN(col) = MIN(col)) 3 (includes the all-NULL group) wrong
projection column + outer WHERE 2 correct

This was discovered by SQLancer's DQR (Derived Query Relocation) equivalence oracle: it relocates a HAVING predicate into a derived-table projection and asserts both forms return the same rows. They do not — the HAVING form retains the NULL-aggregate groups.

How to Reproduce?

CREATE TABLE t0(c0timestamp TIMESTAMP NULL);
INSERT INTO t0 VALUES ('2020-01-01 10:00:00'), ('2020-05-05 08:30:00'), (NULL);

-- HAVING form: returns THREE rows, including the all-NULL group
SELECT t0.c0timestamp FROM t0
GROUP BY t0.c0timestamp
HAVING (MIN(t0.c0timestamp) = MIN(t0.c0timestamp));
-- 2020-01-01 10:00:00.000000
-- 2020-05-05 08:30:00.000000
-- NULL                          <-- WRONG: MIN is NULL, NULL = NULL is NULL, group must be dropped

-- Projection form (same expression, same grouping): returns TWO rows
SELECT ref0 FROM (
    SELECT t0.c0timestamp AS ref0,
           (MIN(t0.c0timestamp) = MIN(t0.c0timestamp)) AS ref1
    FROM t0 GROUP BY t0.c0timestamp
) AS s WHERE ref1;
-- 2020-01-01 10:00:00.000000
-- 2020-05-05 08:30:00.000000    <-- correct

MAX behaves identically to MIN. The divergence also reproduces with GROUP BY GROUPING SETS ((col), ()) and a conjoined GROUPING(col) = 0 atom — the super-aggregate context does not change the outcome.

Cluster information: single node, local datafuselabs/databend Docker container (MySQL protocol). No special configuration.

Additional analysis

  • A bare column self-comparison HAVING (c0timestamp = c0timestamp) behaves identically in BOTH positions (both keep the NULL group), so the HAVING path mishandles x = x with two-valued logic generally — but only the aggregate form diverges from the projection path, which is what makes the defect observable as a wrong result.
  • The projection/materialization path produces NULL for NULL inputs and correctly filters the group; the HAVING filter path does not.

Are you willing to submit PR?

  • Yes I am willing to submit a PR!

Activity

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

Metadata

Metadata

Assignees

Labels

C-bugCategory: something isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions