In PostgreSQL what does hashed subplan mean?

Viewed 1014

I want to know how the optimizer rewrote the query and how to read the execution plan in PostgreSQL Here is the sample code.

DROP TABLE ords;
CREATE TABLE ords (
ORD_ID INT NOT NULL,
ORD_PROD_ID VARCHAR(2) NOT NULL,
ETC_CONTENT VARCHAR(100));
ALTER TABLE ords ADD CONSTRAINT ords_PK PRIMARY KEY(ORD_ID);
CREATE INDEX ords_X01 ON ords(ORD_PROD_ID);
INSERT INTO ords
SELECT i
      ,chr(64+case when i <= 10 then i else 26 end)
      ,rpad('x',100,'x')
  FROM generate_series(1,10000) a(i);

SELECT COUNT(*) FROM ords WHERE ORD_PROD_ID IN ('A','B','C');

DROP TABLE delivery;
CREATE TABLE delivery (
ORD_ID INT NOT NULL,
VEHICLE_ID VARCHAR(2) NOT NULL,
ETC_REMARKS VARCHAR(100));
ALTER TABLE delivery ADD CONSTRAINT delivery_PK primary key (ORD_ID, VEHICLE_ID);
CREATE INDEX delivery_X01 ON delivery(VEHICLE_ID);
INSERT INTO delivery
SELECT i
     , chr(88 + case when i <= 10 then mod(i,2) else 2 end)
     , rpad('x',100,'x')
  FROM generate_series(1,10000) a(i);

analyze ords;
analyze delivery;

This is the SQL I am interested in.

SELECT *
  FROM ords a
 WHERE ( EXISTS (SELECT 1
                   FROM delivery b
                  WHERE a.ORD_ID = b.ORD_ID
                    AND b.VEHICLE_ID IN ('X','Y')
                 )
         OR a.ORD_PROD_ID IN ('A','B','C')
         );
Here is the execution plan
| Seq Scan on portal.ords a (actual time=0.038..2.027 rows=10 loops=1)                                           |
|   Output: a.ord_id, a.ord_prod_id, a.etc_content                                                               |
|   Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) OR ((a.ord_prod_id)::text = ANY ('{A,B,C}'::text[]))) |
|   Rows Removed by Filter: 9990                                                                                 |
|   Buffers: shared hit=181                                                                                      |
|   SubPlan 1                                                                                                    |
|     ->  Index Only Scan using delivery_pk on portal.delivery b (never executed)                                |
|           Index Cond: (b.ord_id = a.ord_id)                                                                    |
|           Filter: ((b.vehicle_id)::text = ANY ('{X,Y}'::text[]))                                               |
|           Heap Fetches: 0                                                                                      |
|   SubPlan 2                                                                                                    |
|     ->  Index Scan using delivery_x01 on portal.delivery b_1 (actual time=0.023..0.025 rows=10 loops=1)        |
|           Output: b_1.ord_id                                                                                   |
|           Index Cond: ((b_1.vehicle_id)::text = ANY ('{X,Y}'::text[]))                                         |
|           Buffers: shared hit=8                                                                                |
| Planning:                                                                                                      |
|   Buffers: shared hit=78                                                                                       |
| Planning Time: 0.302 ms                                                                                        |
| Execution Time: 2.121 ms  

I don't know how the optimizer transformed the SQL.

  • What is the final SQL the optimizer rewrote?
  • I have only one EXISTS sub-query in the SQL above, why are there two sub-plans?
  • What does "hashed Sub-Plan 2" mean?

I would appreciate it if anyone share a little knowledge with me.

1 Answers

You have the misconception that the optimizer rewrites the SQL statement. That is not the case. Rewriting the query is the job of the query rewriter, which for example replaces views with their definition. The optimizer comes up with a sequence of execution steps to compute the result. It produces a plan, not an SQL statement.

The optimizer plans two alternatives: either execute subplan 1 for each row found, or execute subplan 2 once (note that it is independent of a), build a hash table from the result and probe that hash for each row found in a.

At execution time, PostgreSQL decides to use the latter strategy, that is why subplan 1 is never executed.

Related