Skip to Main Content

Oracle Database Free

Announcement

For appeals, questions and feedback about Oracle Forums, please email oracle-forums-moderators_us@oracle.com. Technical questions should be asked in the appropriate category. Thank you!

Oracle fails to short-circuit in WHERE clause with mutiple conditions connected by AND

jinhui laiJun 27 2025 — edited Jul 1 2025

Hi, Oracle developers, please consider such a query:

SELECT * FROM t0 WHERE c0>1 AND (SELECT COUNT(c0) FROM t0, t1, t2)>0;

There are two conditions in the WHERE clause, connected by AND, which means that if any one of the conditions is not satisfied, it is false. Therefore, when we calculate two conditions, if we calculate one of the conditions to be false, we can short-circuit, which can avoid unnecessary calculations. However, Oracle does not short-circuit to optimize such queries. Specially, short-circuiting is necessary when the computation of the condition is expensive. You can reproduce it as follows:

1.Start oralce service

docker run -d -p 1521:1521 -e ORACLE_PWD=1213 --name oracle container-registry.oracle.com/database/free:latest
docker exec -it oracle /bin/bash
sqlplus / as sysdba

2. Create table and insert data.

CREATE TABLE t0(c0 NUMBER(10));
CREATE TABLE t1(c1 NUMBER(10));
CREATE TABLE t2(c2 NUMBER(10));
INSERT INTO t0 VALUES(1);
INSERT INTO t1 SELECT LEVEL FROM dual CONNECT BY LEVEL <= 10000;
INSERT INTO t2 SELECT LEVEL FROM dual CONNECT BY LEVEL <= 100000;

3. Execute queries

-- false positive (lucky case)
SELECT * FROM t0 WHERE c0>1 AND (SELECT COUNT(*) FROM t0, t1, t2)>0;
no rows selected
Elapsed: 00:00:00.00
Execution Plan
----------------------------------------------------------
Plan hash value: 3753548476

-----------------------------------------------------------------------------
| Id  | Operation            | Name | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |      |     1 |     3 |     8   (0)| 00:00:01 |
|*  1 |  FILTER              |      |       |       |            |          |
|*  2 |   TABLE ACCESS FULL  | T0   |     1 |     3 |     2   (0)| 00:00:01 |
|   3 |   NESTED LOOPS       |      |     1 |       |     6   (0)| 00:00:01 |
|   4 |    NESTED LOOPS      |      |     1 |       |     4   (0)| 00:00:01 |
|   5 |     TABLE ACCESS FULL| T0   |     1 |       |     2   (0)| 00:00:01 |
|   6 |     TABLE ACCESS FULL| T2   |     1 |       |     2   (0)| 00:00:01 |
|   7 |    TABLE ACCESS FULL | T1   | 10000 |       |     2   (0)| 00:00:01 |
-----------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter( EXISTS (SELECT 0 FROM "T2" "T2","T1" "T1","T0" "T0"))
   2 - filter("C0">1)
-- negative case: expect 00:00:00.00 but consume 00:00:31.44
-- change "SELECT COUNT(*)" to "SELECT COUNT(c0)" 
SELECT * FROM t0 WHERE c0>1 AND (SELECT COUNT(c0) FROM t0, t1, t2)>0;
no rows selected
Elapsed: 00:00:31.44

Execution Plan
----------------------------------------------------------
Plan hash value: 1536065674

--------------------------------------------------------------------------------
| Id  | Operation               | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT        |      |     1 |     3 |  1244K  (1)| 00:00:49 |
|*  1 |  FILTER                 |      |       |       |            |          |
|*  2 |   TABLE ACCESS FULL     | T0   |     1 |     3 |     2   (0)| 00:00:01 |
|   3 |   SORT AGGREGATE        |      |     1 |     3 |            |          |
|   4 |    MERGE JOIN CARTESIAN |      |  3000M|  8583M|  1244K  (1)| 00:00:49 |
|   5 |     MERGE JOIN CARTESIAN|      | 30000 | 90000 |    20   (0)| 00:00:01 |
|   6 |      TABLE ACCESS FULL  | T0   |     3 |     9 |     2   (0)| 00:00:01 |
|   7 |      BUFFER SORT        |      | 10000 |       |    18   (0)| 00:00:01 |
|   8 |       TABLE ACCESS FULL | T1   | 10000 |       |     6   (0)| 00:00:01 |
|   9 |     BUFFER SORT         |      |   100K|       |  1244K  (1)| 00:00:49 |
|  10 |      TABLE ACCESS FULL  | T2   |   100K|       |    41   (0)| 00:00:01 |
--------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter( (SELECT COUNT("C0") FROM "T2" "T2","T1" "T1","T0"
              "T0")>0)
   2 - filter("C0">1)
-- negative case: expect 00:00:00.00 but consume 00:00:25.35
-- change SELECT ">0" to ">1" 
SELECT * FROM t0 WHERE c0>1 AND (SELECT COUNT(*) FROM t0, t1, t2)>1;
no rows selected
Elapsed: 00:00:25.35
Execution Plan
----------------------------------------------------------
Plan hash value: 1536065674

--------------------------------------------------------------------------------
| Id  | Operation               | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT        |      |     1 |     3 |  1244K  (1)| 00:00:49 |
|*  1 |  FILTER                 |      |       |       |            |          |
|*  2 |   TABLE ACCESS FULL     | T0   |     1 |     3 |     2   (0)| 00:00:01 |
|   3 |   SORT AGGREGATE        |      |     1 |       |            |          |
|   4 |    MERGE JOIN CARTESIAN |      |  3000M|       |  1244K  (1)| 00:00:49 |
|   5 |     MERGE JOIN CARTESIAN|      | 30000 |       |    20   (0)| 00:00:01 |
|   6 |      TABLE ACCESS FULL  | T0   |     3 |       |     2   (0)| 00:00:01 |
|   7 |      BUFFER SORT        |      | 10000 |       |    18   (0)| 00:00:01 |
|   8 |       TABLE ACCESS FULL | T1   | 10000 |       |     6   (0)| 00:00:01 |
|   9 |     BUFFER SORT         |      |   100K|       |  1244K  (1)| 00:00:49 |
|  10 |      TABLE ACCESS FULL  | T2   |   100K|       |    41   (0)| 00:00:01 |
--------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter( (SELECT COUNT(*) FROM "T2" "T2","T1" "T1","T0" "T0")>1)
   2 - filter("C0">1)

COUNT (*)>0 can be optimized for existence checking, so the first query above is a lucky case rather than applying short-circuit optimization. COUNT (*)>1 needs to calculate the number of rows and COUNT (column)>0 needs to check if it is NULL, so the second and third queries consume much time.

All of the above three queries use two filters separately which make cannot short-circuit ( false AND anyother is false). The follows is a positive case where only one filter is used, which can short-circuit( true OR anyother is true):

-- positive case: it only uses one filter and makes "c0=1" fronted 
SELECT * FROM t0 WHERE c0=1 OR (SELECT COUNT(c0) FROM t0, t1, t2)>1;

        C0
----------
         1

Elapsed: 00:00:00.00

Execution Plan
----------------------------------------------------------
Plan hash value: 873585208

--------------------------------------------------------------------------------
| Id  | Operation               | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT        |      |     3 |     9 |     2   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL      | T0   |     3 |     9 |     2   (0)| 00:00:01 |
|   2 |   SORT AGGREGATE        |      |     1 |     3 |            |          |
|   3 |    MERGE JOIN CARTESIAN |      |  3000M|  8583M|  1244K  (1)| 00:00:49 |
|   4 |     MERGE JOIN CARTESIAN|      | 30000 | 90000 |    20   (0)| 00:00:01 |
|   5 |      TABLE ACCESS FULL  | T0   |     3 |     9 |     2   (0)| 00:00:01 |
|   6 |      BUFFER SORT        |      | 10000 |       |    18   (0)| 00:00:01 |
|   7 |       TABLE ACCESS FULL | T1   | 10000 |       |     6   (0)| 00:00:01 |
|   8 |     BUFFER SORT         |      |   100K|       |  1244K  (1)| 00:00:49 |
|   9 |      TABLE ACCESS FULL  | T2   |   100K|       |    41   (0)| 00:00:01 |
--------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("C0"=1 OR  (SELECT COUNT("C0") FROM "T2" "T2","T1"
              "T1","T0" "T0")>1)

Thank you for your time. I look forward to your reply.

Best regards,

Jinhui Lai
Nanchang University

Comments
Post Details
Added on Jun 27 2025
9 comments
184 views