All Products
Search
Document Center

PolarDB:Subquery folding

Last Updated:Mar 28, 2026

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

TypeOperatorExample
EXISTSEXISTS, NOT EXISTSWHERE EXISTS (SELECT 1 FROM t2)
ININ, NOT INWHERE a IN (SELECT a FROM t2)
ANY= ANY, != ANY, < ANY, <= ANY, > ANY, >= ANYWHERE t.a > ANY (SELECT t2.a FROM t2)
ALL= ALL, != ALL, < ALL, <= ALL, > ALL, >= ALLWHERE 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 EXISTS subqueries, or two > ANY subqueries, are same-type subqueries.

  • Mutually exclusive subqueries use opposite operators. The following table shows all mutually exclusive pairs.

SubqueryMutually exclusive subquery
EXISTSNOT EXISTS
INNOT 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:

RelationMeaning
Left subsetThe left set is a proper subset of the right set
Right subsetThe right set is a proper subset of the left set
EqualThe two sets contain the same elements
UncomparableNeither 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);                   -- subq2

Prerequisites

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:

ParameterScopeDefaultDescription
loose_polar_optimizer_switchGlobalcoalesce_subquery=offEnables or disables subquery folding. Set to coalesce_subquery=on to enable.
force_coalesce_subqueryGlobal / sessionOFFForces 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 in WHERE, HAVING, or JOIN ON conditions, including under both AND and OR operators.

Folding rules

Same-type subqueries

AND operator

Subquery typesInclusion relationResult
Both: EXISTS, IN, ANY, or ALLLeft subset or equalRemove — right subquery dropped, left retained
Both: EXISTS, IN, ANY, or ALLRight subsetRemove — left subquery dropped, right retained
Both: NOT EXISTS, NOT IN, or != ALLUncomparableMerge (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 typesInclusion relationResult
Both: EXISTS, IN, ANY, or ALLLeft subset or equalRemove — left subquery dropped, right retained
Both: EXISTS, IN, ANY, or ALLRight subsetRemove — right subquery dropped, left retained
Both: EXISTS, IN, or ANYUncomparableMerge (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 typesInclusion relationMerge conditionsResult
EXISTS + NOT EXISTS; IN + NOT INLeft subset or equalRemove — AND condition rewritten to FALSE
EXISTS + NOT EXISTSRight subsetQuery block cannot be UNION; only WHERE conditions differ; nested subqueries supportedMerge (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 + < ALLLeft subset or equalRemove — AND condition rewritten to FALSE
IN + NOT IN; = ANY + != ALLRight subsetQuery block cannot be UNION; only WHERE or HAVING conditions differ; nested subqueries supportedMerge (always optimal) — sets merged, LNNVL operator added. Applied by default without needing force_coalesce_subquery.

OR operator

Subquery typesInclusion relationResult
EXISTS + NOT EXISTSRight subsetRemove — 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 2

Subquery 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 2

Subquery 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 2

Subquery 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 set

The 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 2

Subquery 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 2

The 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.

image

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.