Bug #121239 Wrong result: the same CTE returns inconsistent cardinality across consumers with UNION/EXCEPT

Submitted: 7 Sep 1:17 Modified: 7 Sep 7:30
Reporter: b a Email Updates:
Status: Documenting Impact on me:
None 
Category:MySQL Server: Optimizer Severity:S2 (Serious)
Version:8.0.46 OS:Any
Assigned to: CPU Architecture:Any
Tags: cte, except, Optimizer, UNION, wrong result

[7 Sep 1:17] b a
Description:
A non-recursive CTE containing UNION and EXCEPT produces inconsistent results when the same CTE is consumed by multiple query blocks in the same statement.

The CTE p contains exactly one row when evaluated independently.

However, when p is referenced both directly and through another CTE q, two consumers of the same CTE observe different cardinalities in the same statement:

the direct consumer reports COUNT(*) = 0
the consumer through q reports COUNT(*) = 1

Both consumers refer to the same CTE p, so they should observe the same relational result.

The issue is reproducible without any tables or user data and causes a silent wrong result.

Tested on MySQL 26.7.0.

How to repeat:
First, verify the set operation independently:

SELECT COUNT(*) FROM (
    SELECT 'A' AS x
    UNION SELECT 'A'
    EXCEPT SELECT NULL WHERE FALSE
) AS p;

Result:

+----------+
| COUNT(*) |
+----------+
|        1 |
+----------+
1 row in set

This is correct. UNION removes the duplicate 'A', and the right-hand side of EXCEPT is empty.

Next, evaluate the same expression through a CTE:

WITH p AS (
    SELECT 'A' AS x
    UNION SELECT 'A'
    EXCEPT SELECT NULL WHERE FALSE
)
SELECT COUNT(*) FROM p;

Result:

+----------+
| COUNT(*) |
+----------+
|        1 |
+----------+
1 row in set

This is also correct.

Now reference the same CTE p from two different consumers in the same statement:

WITH p AS (
    SELECT 'A' AS x
    UNION SELECT 'A'
    EXCEPT SELECT NULL WHERE FALSE
), q AS (
    SELECT COUNT(*) AS y FROM p
)
SELECT
    (SELECT COUNT(*) FROM p) AS direct_count,
    (SELECT y FROM q) AS count_via_q;

Actual result on MySQL 26.7.0:

+--------------+-------------+
| direct_count | count_via_q |
+--------------+-------------+
|            0 |           1 |
+--------------+-------------+
1 row in set

Expected result:

+--------------+-------------+
| direct_count | count_via_q |
+--------------+-------------+
|            1 |           1 |
+--------------+-------------+

The two scalar subqueries consume the same CTE p.

Therefore, direct_count and count_via_q should both observe the same cardinality.

The standalone control queries also demonstrate that the correct cardinality of p is 1.

For completeness:

SELECT VERSION();

returns:

+-----------+
| VERSION() |
+-----------+
| 26.7.0    |
+-----------+
1 row in set

No tables, schema objects, or user data are required to reproduce the issue.
[7 Sep 7:30] Roy Lyseng
Thank you for the bug report.
Verified as described.
[16 Oct 2:48] MySQL Admin
Posted by developer: Bug status updated to 'Patch pending'
[16 Oct 13:49] OCA Admin
Contribution submitted via Github - Bug#121239: Preserve set-operation state across shared CTE references 
(*) Contribution by PR specific confirm no longer needed

Contribution: git_patch_4588022553.txt (text/plain), 8.02 KiB.

[23 Oct 8:48] MySQL Admin
Posted by developer: Bug status updated to 'Patch approved'
[24 Oct 2:48] MySQL Admin
Posted by developer: Bug status updated to 'Documenting'

Portions of this website are copyright © 2001, 2002 The PHP Group

Page generated in 0.056 sec. using MySQL 8.4.7-u6-cloud

Timestamp references displayed by the system are UTC.
Content reproduced on this site is the property of the respective copyright holders.
It is not reviewed in advance by Oracle and does not necessarily represent the opinion of Oracle or any other party.