The following query is part of a much bigger one that runs perfectly fast on a filled DB but on a nearly empty one it is very long.
In this simplified form, it takes ~400ms to execute but if you remove either line (1) or lines (2) and (3) then it takes ~35ms. Why ? And how do I make it work normally ?
Some background about the DB :
- DB is VACUUMed andANALYZEd
- ctractis empty
- contratscontains only 2 lines, none of which has a- idtypecontratIN (4,5)
- so tmpctr1is empty
- copyrightadcontains 280 rows, only one matches the filters- idoeu=13and- role IN ('E','CE')
- in all cases, query returns ONE row (the one returned by the first part of the recursive CTE)
- line (1) is absolutely not used in this version but removing it hides the problem for some reason
    WITH RECURSIVE tmpctr1 AS (
        SELECT ced.idad AS cedant, ced.idclient
        FROM contrats c
        JOIN CtrAct ced ON c.idcontrat=ced.idcontrat AND ced.isassignor
        JOIN CtrAct ces ON c.idcontrat=ces.idcontrat AND NOT COALESCE(ces.isassignor,FALSE)  --(1)
        WHERE idtypecontrat IN (4,5)
    )
    ,rec1 AS (
        SELECT ca.idoeu,ca.idad AS chn,1 AS idclient, 1 AS level
        FROM copyrightad ca 
        WHERE ca.role IN ('E','CE')
        AND ca.idoeu = 13
        UNION
        SELECT r.idoeu,0, 0, r.level+1
        FROM rec1 r
        LEFT JOIN tmpctr1 c ON r.chn=c.cedant
        LEFT JOIN tmpctr1 c2 ON r.idclient=c2.idclient  -- (2)
        WHERE r.level<20
        AND (c.cedant is not null 
          OR c2.cedant is not null  --(3)
        )
    )
    select * from rec1
Query plan #1 : slow
QUERY PLAN
CTE Scan on rec1  (cost=1662106.61..2431078.65 rows=38448602 width=16) (actual time=384.975..398.182 rows=1 loops=1)    
  CTE tmpctr1   
    ->  Hash Join  (cost=36.06..116.37 rows=148225 width=8) (actual time=0.009..0.010 rows=0 loops=1)   
          Hash Cond: (c.idcontrat = ces.idcontrat)  
          ->  Hash Join  (cost=1.04..28.50 rows=385 width=16) (actual time=0.009..0.009 rows=0 loops=1) 
                Hash Cond: (ced.idcontrat = c.idcontrat)    
                ->  Seq Scan on ctract ced  (cost=0.00..25.40 rows=770 width=12) (actual time=0.008..0.008 rows=0 loops=1)  
                      Filter: isassignor    
                ->  Hash  (cost=1.02..1.02 rows=1 width=4) (never executed) 
                      ->  Seq Scan on contrats c  (cost=0.00..1.02 rows=1 width=4) (never executed) 
                            Filter: (idtypecontrat = ANY ('{4,5}'::integer[]))  
          ->  Hash  (cost=25.40..25.40 rows=770 width=4) (never executed)   
                ->  Seq Scan on ctract ces  (cost=0.00..25.40 rows=770 width=4) (never executed)    
                      Filter: (NOT COALESCE(isassignor, false)) 
  CTE rec1  
    ->  Recursive Union  (cost=0.00..1661990.25 rows=38448602 width=16) (actual time=384.973..398.179 rows=1 loops=1)   
          ->  Seq Scan on copyrightad ca  (cost=0.00..8.20 rows=2 width=16) (actual time=384.970..384.981 rows=1 loops=1)   
                Filter: (((role)::text = ANY ('{E,CE}'::text[])) AND (idoeu = 13))  
                Rows Removed by Filter: 279 
          ->  Merge Left Join  (cost=21618.01..89301.00 rows=3844860 width=16) (actual time=13.193..13.193 rows=0 loops=1)  
                Merge Cond: (r.idclient = c2.idclient)  
                Filter: ((c_1.cedant IS NOT NULL) OR (c2.cedant IS NOT NULL))   
                Rows Removed by Filter: 1   
                ->  Sort  (cost=3892.89..3905.86 rows=5188 width=16) (actual time=13.179..13.180 rows=1 loops=1)    
                      Sort Key: r.idclient  
                      Sort Method: quicksort  Memory: 25kB  
                      ->  Hash Right Join  (cost=0.54..3572.76 rows=5188 width=16) (actual time=13.170..13.171 rows=1 loops=1)  
                            Hash Cond: (c_1.cedant = r.chn) 
                            ->  CTE Scan on tmpctr1 c_1  (cost=0.00..2964.50 rows=148225 width=4) (actual time=0.011..0.011 rows=0 loops=1) 
                            ->  Hash  (cost=0.45..0.45 rows=7 width=16) (actual time=13.150..13.150 rows=1 loops=1) 
                                  Buckets: 1024  Batches: 1  Memory Usage: 9kB  
                                  ->  WorkTable Scan on rec1 r  (cost=0.00..0.45 rows=7 width=16) (actual time=13.138..13.140 rows=1 loops=1)   
                                        Filter: (level < 20)    
                ->  Materialize  (cost=17725.12..18466.25 rows=148225 width=8) (actual time=0.008..0.008 rows=0 loops=1)    
                      ->  Sort  (cost=17725.12..18095.68 rows=148225 width=8) (actual time=0.007..0.007 rows=0 loops=1) 
                            Sort Key: c2.idclient   
                            Sort Method: quicksort  Memory: 25kB    
                            ->  CTE Scan on tmpctr1 c2  (cost=0.00..2964.50 rows=148225 width=8) (actual time=0.000..0.000 rows=0 loops=1)  
Planning Time: 0.270 ms 
JIT:    
  Functions: 53 
  Options: Inlining true, Optimization true, Expressions true, Deforming true   
  Timing: Generation 5.064 ms, Inlining 4.491 ms, Optimization 236.336 ms, Emission 155.206 ms, Total 401.097 ms    
Execution Time: 403.549 ms  
Query plan #2 : fast : line (1) is hidden
QUERY PLAN
CTE Scan on rec1  (cost=240.86..245.90 rows=252 width=16) (actual time=0.030..0.058 rows=1 loops=1) 
  CTE tmpctr1   
    ->  Hash Join  (cost=1.04..28.50 rows=385 width=8) (actual time=0.001..0.001 rows=0 loops=1)    
          Hash Cond: (ced.idcontrat = c.idcontrat)  
          ->  Seq Scan on ctract ced  (cost=0.00..25.40 rows=770 width=12) (actual time=0.001..0.001 rows=0 loops=1)    
                Filter: isassignor  
          ->  Hash  (cost=1.02..1.02 rows=1 width=4) (never executed)   
                ->  Seq Scan on contrats c  (cost=0.00..1.02 rows=1 width=4) (never executed)   
                      Filter: (idtypecontrat = ANY ('{4,5}'::integer[]))    
  CTE rec1  
    ->  Recursive Union  (cost=0.00..212.35 rows=252 width=16) (actual time=0.029..0.056 rows=1 loops=1)    
          ->  Seq Scan on copyrightad ca  (cost=0.00..8.20 rows=2 width=16) (actual time=0.027..0.041 rows=1 loops=1)   
                Filter: (((role)::text = ANY ('{E,CE}'::text[])) AND (idoeu = 13))  
                Rows Removed by Filter: 279 
          ->  Hash Right Join  (cost=9.97..19.91 rows=25 width=16) (actual time=0.013..0.013 rows=0 loops=1)    
                Hash Cond: (c2.idclient = r.idclient)   
                Filter: ((c_1.cedant IS NOT NULL) OR (c2.cedant IS NOT NULL))   
                Rows Removed by Filter: 1   
                ->  CTE Scan on tmpctr1 c2  (cost=0.00..7.70 rows=385 width=8) (actual time=0.000..0.000 rows=0 loops=1)    
                ->  Hash  (cost=9.81..9.81 rows=13 width=16) (actual time=0.009..0.009 rows=1 loops=1)  
                      Buckets: 1024  Batches: 1  Memory Usage: 9kB  
                      ->  Hash Right Join  (cost=0.54..9.81 rows=13 width=16) (actual time=0.008..0.008 rows=1 loops=1) 
                            Hash Cond: (c_1.cedant = r.chn) 
                            ->  CTE Scan on tmpctr1 c_1  (cost=0.00..7.70 rows=385 width=4) (actual time=0.001..0.001 rows=0 loops=1)   
                            ->  Hash  (cost=0.45..0.45 rows=7 width=16) (actual time=0.003..0.003 rows=1 loops=1)   
                                  Buckets: 1024  Batches: 1  Memory Usage: 9kB  
                                  ->  WorkTable Scan on rec1 r  (cost=0.00..0.45 rows=7 width=16) (actual time=0.002..0.002 rows=1 loops=1) 
                                        Filter: (level < 20)    
Planning Time: 0.330 ms 
Execution Time: 0.094 ms    
Query plan #3 : fast : lines (2) and (3) are hidden
QUERY PLAN
CTE Scan on rec1  (cost=1829.46..2907.50 rows=53902 width=16) (actual time=0.050..0.074 rows=1 loops=1) 
  CTE rec1  
    ->  Recursive Union  (cost=0.00..1829.46 rows=53902 width=16) (actual time=0.049..0.072 rows=1 loops=1) 
          ->  Seq Scan on copyrightad ca  (cost=0.00..8.20 rows=2 width=16) (actual time=0.046..0.067 rows=1 loops=1)   
                Filter: (((role)::text = ANY ('{E,CE}'::text[])) AND (idoeu = 13))  
                Rows Removed by Filter: 279 
          ->  Hash Join  (cost=30.45..74.32 rows=5390 width=16) (actual time=0.003..0.003 rows=0 loops=1)   
                Hash Cond: (c.idcontrat = ced.idcontrat)    
                ->  Hash Join  (cost=1.04..28.50 rows=385 width=8) (actual time=0.002..0.002 rows=0 loops=1)    
                      Hash Cond: (ces.idcontrat = c.idcontrat)  
                      ->  Seq Scan on ctract ces  (cost=0.00..25.40 rows=770 width=4) (actual time=0.002..0.002 rows=0 loops=1) 
                            Filter: (NOT COALESCE(isassignor, false))   
                      ->  Hash  (cost=1.02..1.02 rows=1 width=4) (never executed)   
                            ->  Seq Scan on contrats c  (cost=0.00..1.02 rows=1 width=4) (never executed)   
                                  Filter: (idtypecontrat = ANY ('{4,5}'::integer[]))    
                ->  Hash  (cost=29.08..29.08 rows=27 width=12) (never executed) 
                      ->  Hash Join  (cost=0.54..29.08 rows=27 width=12) (never executed)   
                            Hash Cond: (ced.idad = r.chn)   
                            ->  Seq Scan on ctract ced  (cost=0.00..25.40 rows=766 width=8) (never executed)    
                                  Filter: (isassignor AND (idad IS NOT NULL))   
                            ->  Hash  (cost=0.45..0.45 rows=7 width=12) (never executed)    
                                  ->  WorkTable Scan on rec1 r  (cost=0.00..0.45 rows=7 width=12) (never executed)  
                                        Filter: (level < 20)    
Planning Time: 0.310 ms 
Execution Time: 0.179 ms    
PostgreSQL 12.2
Edit: the same query on the same DB on PostgreSQL 11.6 runs fast (still highly over-estimating rows on some parts) so I guess this is a regression.
 
    