Subquery folding reduces the number of subqueries in an SQL statement by merging or eliminating redundant ones, lowering execution overhead without changing query results.
How it works
When two subqueries query overlapping sets, the optimizer can eliminate or combine them:
Remove — one subquery is dropped entirely because its result is guaranteed by the other.
Merge — the conditions of two subqueries are combined into a single subquery.
The optimizer applies folding rules based on two properties of the subquery pair: their type relationship (same-type or mutually exclusive) and the inclusion relation between their result sets.
Key concepts
Supported subquery types
| Type | Operator | Example |
|---|---|---|
| EXISTS | EXISTS, NOT EXISTS | WHERE EXISTS (SELECT 1 FROM t2) |
| IN | IN, NOT IN | WHERE a IN (SELECT a FROM t2) |
| ANY | = ANY, != ANY, < ANY, <= ANY, > ANY, >= ANY | WHERE t.a > ANY (SELECT t2.a FROM t2) |
| ALL | = ALL, != ALL, < ALL, <= ALL, > ALL, >= ALL | WHERE t.a > ANY (SELECT t2.a FROM t2) |
Single-row scalar subqueries (for example, WHERE t.a < (SELECT MIN(t2.a) ...)) are not supported.Same-type and mutually exclusive subqueries
Same-type subqueries share the same operator. Two
EXISTSsubqueries, or two> ANYsubqueries, are same-type subqueries.Mutually exclusive subqueries use opposite operators. The following table shows all mutually exclusive pairs.
| Subquery | Mutually exclusive subquery |
|---|---|
EXISTS | NOT EXISTS |
IN | NOT IN |
= ANY | != ALL |
!= ANY | = ALL |
< ANY | >= ALL or > ALL |
<= ANY | > ALL |
> ANY | <= ALL or < ALL |
>= ANY | < ALL |
These equivalences reflect the underlying logical identities: IN is equivalent to = ANY, and NOT IN is equivalent to != ALL. Understanding them makes the mutually exclusive pairs easier to internalize.
Inclusion relations
The right side of a subquery is a set. Two sets can have four relations:
| Relation | Meaning |
|---|---|
| Left subset | The left set is a proper subset of the right set |
| Right subset | The right set is a proper subset of the left set |
| Equal | The two sets contain the same elements |
| Uncomparable | Neither set contains the other |
Example of a left subset: In the following query, subq1 applies the extra condition t2.a > 10, so its result is always a subset of subq2.
SELECT a FROM t
WHERE EXISTS (SELECT /*+ subq1 */ t2.a FROM t2 WHERE t2.a > 10) -- subq1
AND EXISTS (SELECT /*+ subq2 */ t2.a FROM t2); -- subq2Prerequisites
Before you begin, ensure that you have:
A PolarDB for MySQL 8.0 cluster at revision version 8.0.2.2.23 or later
To check your cluster version, see Engine versions 5.6, 5.7, and 8.0.
Enable subquery folding
Two parameters control folding behavior:
| Parameter | Scope | Default | Description |
|---|---|---|---|
loose_polar_optimizer_switch | Global | coalesce_subquery=off | Enables or disables subquery folding. Set to coalesce_subquery=on to enable. |
force_coalesce_subquery | Global / session | OFF | Forces merge operations that are marked as not always optimal. The optimizer's Cost Based Query Transformation (CBQT) component normally decides whether merging improves performance; setting this to ON bypasses that check. |
Enable subquery folding:
SET loose_polar_optimizer_switch = 'coalesce_subquery=on';Force subquery merging in the current session (use only after confirming the merge is beneficial):
SET force_coalesce_subquery = ON;Target specific subqueries with the HINT syntax — name query blocks with QB_NAME and specify which pairs to fold with SUBQUERY_COALESCE:
DESC SELECT /*+ SUBQUERY_COALESCE(qb1, qb2) SUBQUERY_COALESCE(qb3, qb4) */
*
FROM t1
LEFT JOIN t2 ON t1.a = ANY (SELECT /*+ QB_NAME(qb1) */ a FROM t2)
AND t1.a != ALL (SELECT /*+ QB_NAME(qb2) */ a FROM t2 WHERE a < 100)
HAVING t1.b = ANY (SELECT /*+ QB_NAME(qb3) */ b FROM t2)
AND t1.b != ALL (SELECT /*+ QB_NAME(qb4) */ b FROM t2 WHERE b < 1);Folded objects can appear in any position inWHERE,HAVING, orJOIN ONconditions, including under bothANDandORoperators.
Folding rules
Same-type subqueries
AND operator
| Subquery types | Inclusion relation | Result |
|---|---|---|
| Both: EXISTS, IN, ANY, or ALL | Left subset or equal | Remove — right subquery dropped, left retained |
| Both: EXISTS, IN, ANY, or ALL | Right subset | Remove — left subquery dropped, right retained |
Both: NOT EXISTS, NOT IN, or != ALL | Uncomparable | Merge (not always optimal) — WHERE or HAVING conditions combined into one subquery. Requires SPJ subqueries or subqueries with only SPJ and HAVING conditions. Subqueries with only WHERE conditions or inconsistent HAVING conditions are also supported. |
OR operator
| Subquery types | Inclusion relation | Result |
|---|---|---|
| Both: EXISTS, IN, ANY, or ALL | Left subset or equal | Remove — left subquery dropped, right retained |
| Both: EXISTS, IN, ANY, or ALL | Right subset | Remove — right subquery dropped, left retained |
| Both: EXISTS, IN, or ANY | Uncomparable | Merge (not always optimal) — WHERE or HAVING conditions combined into one subquery. Requires SPJ subqueries or subqueries with only SPJ and HAVING conditions. Subqueries with only WHERE conditions or inconsistent HAVING conditions are also supported. |
Mutually exclusive subqueries
AND operator
| Subquery types | Inclusion relation | Merge conditions | Result |
|---|---|---|---|
| EXISTS + NOT EXISTS; IN + NOT IN | Left subset or equal | — | Remove — AND condition rewritten to FALSE |
| EXISTS + NOT EXISTS | Right subset | Query block cannot be UNION; only WHERE conditions differ; nested subqueries supported | Merge (not always optimal) — sets merged, HAVING SUM(CASE WHEN extra_cond THEN 1 ELSE 0 END) ==0 added |
!= ANY + = ALL; < ANY + >= ALL or > ALL; <= ANY + > ALL; > ANY + <= ALL or < ALL; >= ANY + < ALL | Left subset or equal | — | Remove — AND condition rewritten to FALSE |
IN + NOT IN; = ANY + != ALL | Right subset | Query block cannot be UNION; only WHERE or HAVING conditions differ; nested subqueries supported | Merge (always optimal) — sets merged, LNNVL operator added. Applied by default without needing force_coalesce_subquery. |
OR operator
| Subquery types | Inclusion relation | Result |
|---|---|---|
| EXISTS + NOT EXISTS | Right subset | Remove — OR condition rewritten to TRUE |
Examples
Remove same-type subqueries
AND condition
-- Before
SELECT * FROM t1
WHERE EXISTS (SELECT 1 FROM t2 WHERE c2 = 0) -- Subquery 1
AND EXISTS (SELECT 1 FROM t2); -- Subquery 2Subquery 1 is a subset of subquery 2 → subquery 2 is redundant under AND → subquery 2 removed.
-- After
SELECT * FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE c2 = 0);OR condition
-- Before
SELECT * FROM t1
WHERE EXISTS (SELECT 1 FROM t2 WHERE c2 = 0) -- Subquery 1
OR EXISTS (SELECT 1 FROM t2); -- Subquery 2Subquery 1 is a subset of subquery 2 → under OR, the larger set dominates → subquery 1 removed.
-- After
SELECT * FROM t1 WHERE EXISTS (SELECT 1 FROM t2);Merge same-type subqueries
AND condition
-- Before
SELECT * FROM t1
WHERE NOT EXISTS (SELECT t1.a AS f FROM t1 WHERE a > 10 AND b < 10)
AND NOT EXISTS (SELECT a FROM t1 WHERE a > 10 AND c < 3);Both subqueries query the same table with the same base condition (a > 10); their extra conditions are merged with OR.
-- After
SELECT * FROM t1
WHERE NOT EXISTS (SELECT t1.a AS f FROM t1 WHERE a > 10 AND (b < 10 OR c < 3));OR condition
-- Before
SELECT * FROM t1
WHERE EXISTS (SELECT t1.a AS f FROM t1 WHERE a > 10 AND b < 10)
OR EXISTS (SELECT a FROM t1 WHERE a > 10 AND c < 3);Both subqueries query the same table with the same base condition; their extra conditions are merged with OR.
-- After
SELECT * FROM t1
WHERE EXISTS (SELECT t1.a AS f FROM t1 WHERE a > 10 AND (b < 10 OR c < 3));Remove mutually exclusive subqueries
EXISTS and NOT EXISTS — AND condition
-- Before
SELECT * FROM t1
WHERE EXISTS (SELECT 1 FROM t2 WHERE c1 = 0) -- Subquery 1
AND NOT EXISTS (SELECT 1 FROM t2); -- Subquery 2Subquery 1 is a subset of subquery 2. A row cannot satisfy both EXISTS (subset) and NOT EXISTS (superset) at the same time → AND condition is always false.
-- After
SELECT * FROM t1 WHERE false;ANY and ALL conflict — AND condition
Applies to: > ANY + < ALL or <= ALL; < ANY + > ALL or >= ALL.
-- Before
SELECT * FROM t1
WHERE t1.c1 > ANY (SELECT c1 FROM t2 WHERE c1 > 10 AND c2 > 1) -- ANY set
AND t1.c1 < ALL (SELECT c1 FROM t2 WHERE c1 > 10); -- ALL setThe ANY set is a subset of the ALL set. No value can be both greater than some element of the subset and less than all elements of the superset → AND condition is always false.
-- After
SELECT * FROM t1 WHERE false; //The ANY set is a subset of the ALL set.EXISTS and NOT EXISTS — OR condition
-- Before
SELECT * FROM t1
WHERE EXISTS (SELECT 1 FROM t2) -- Subquery 1
OR NOT EXISTS (SELECT 1 FROM t2 WHERE c1 = 0); -- Subquery 2Subquery 2 is a subset of subquery 1. Under OR, if the superset is non-empty the condition is true; if empty, the complement of the subset is always true → OR condition is always true.
-- After
SELECT * FROM t1 WHERE true; // Subquery 2 is a subset of subquery 1.Merge mutually exclusive subqueries
Merge EXISTS and NOT EXISTS
-- Before
SELECT * FROM t1
WHERE EXIST (SELECT 1 FROM t2) -- Subquery 1
AND NOT EXIST (SELECT 1 FROM t2 WHERE c2 = 0); -- Subquery 2The NOT EXISTS set is a right subset of the EXISTS set. The optimizer merges both into one scan and adds a HAVING condition to exclude the rows matched by the NOT EXISTS predicate.
-- After
SELECT * FROM t1
WHERE EXIST (
SELECT 1 FROM t2
HAVING SUM(CASE WHEN extra_cond THEN 1 ELSE 0 END) ==0
);This merge is not always optimal. By default, the CBQT component decides whether to apply it. To force the merge, set force_coalesce_subquery = ON.The following chart shows query duration for TPCH Q21 before and after enabling subquery folding. A shorter bar indicates better performance.

Merge IN and NOT IN (or = ANY and != ALL)
Applies to: IN + NOT IN (NOT IN set is the left subset); = ANY + != ALL (ALL set is the left subset).
-- Before
SELECT * FROM t1
WHERE t1.c1 = ANY (SELECT c1 FROM t2 WHERE c1 > 10) -- = ANY set
AND t1.c1 != ALL (SELECT c1 FROM t2 WHERE c1 > 100); -- != ALL set (left subset)The != ALL set (c1 > 100) is a subset of the = ANY set (c1 > 10). The optimizer folds them by adding the LNNVL condition to the larger subquery, excluding rows that also satisfy the smaller subquery.
-- After
SELECT * FROM t1
WHERE t1.c1 = ANY (SELECT c1 FROM t2 WHERE c1 > 10 AND LNNVL(c1 > 100));This merge is always optimal and is applied by default without setting force_coalesce_subquery.