Thread: [BUGS] BUG #14715: Constraint exclusion isn't used in function usinglanguage sql

[BUGS] BUG #14715: Constraint exclusion isn't used in function usinglanguage sql

From
cliveevans@ntlworld.com
Date:
The following bug has been logged on the website:

Bug reference:      14715
Logged by:          Clive Evans
Email address:      cliveevans@ntlworld.com
PostgreSQL version: 9.6.3
Operating system:   CentOS Linux release 7.3.1611
Description:

The same query written using PL/PGSQL will only scan the expected partition
tables.

For example:
partition_test=# DO                                                                             
$$             
BEGIN                          
CREATE TABLE customer_reviews(   customer_id TEXT,   review_date DATE,   review_rating INTEGER,   review_votes INTEGER,
 review_helpful_votes INTEGER,   product_id CHAR(10),   product_title TEXT,
          product_sales_rank BIGINT,   product_group TEXT,   product_category TEXT,   product_subcategory TEXT,
similar_product_idsCHAR(10)[]
 
);                                
FOR n in 2000..2004
LOOP                                                     EXECUTE 'CREATE TABLE customer_reviews_' || n || ' (check
(review_date>= 
''' || n || '-01-01'' AND review_date < ''' || n + 1 || '-01-01'')) INHERITS
(customer_reviews) ;';
END LOOP;
END                             
$$ ;
DO
Time: 15.647 ms
partition_test=# CREATE OR REPLACE FUNCTION title_vs_review_sql(start_date
DATE, end_date DATE)
RETURNS TABLE (  title_length_bucket INTEGER,  review_average NUMERIC,  count BIGINT
) AS                 
$BODY$                    
SELECT                      width_bucket(length(product_title), 1, 50, 5) title_length_bucket,
round(avg(review_rating),2) AS review_average,   count(*)
 
FROM                            customer_reviews
WHERE                        product_group = 'Book'   AND                              review_date BETWEEN start_date
ANDend_date
 
GROUP BY   title_length_bucket
ORDER BY
                           
title_length_bucket        
$BODY$
LANGUAGE SQL;
CREATE FUNCTION
Time: 0.918 ms
partition_test=# CREATE OR REPLACE FUNCTION
title_vs_review_plpgsql(start_date DATE, end_date DATE)
RETURNS TABLE (  title_length_bucket INTEGER,  review_average NUMERIC,  count BIGINT
) AS
$BODY$
BEGIN
RETURN QUERY       SELECT           width_bucket(length(product_title), 1, 50, 5)
title_length_bucket,           round(avg(review_rating), 2) AS review_average,           count(*)       FROM
customer_reviews      WHERE           product_group = 'Book'           AND           review_date BETWEEN start_date AND
end_date      GROUP BY           title_length_bucket       ORDER BY           title_length_bucket;
 
END;
$BODY$
LANGUAGE PLPGSQL;
CREATE FUNCTION
Time: 1.375 ms
partition_test=# set auto_explain.log_min_duration to 0;
SET
Time: 0.190 ms
partition_test=# set auto_explain.log_nested_statements to true ;
SET
Time: 0.197 ms
partition_test=# select title_vs_review_plpgsql('2001-01-01',
'2001-12-31');title_vs_review_plpgsql 
-------------------------
(0 rows)

Time: 0.899 ms
partition_test=# select title_vs_review_sql('2001-01-01', '2001-12-31');title_vs_review_sql 
---------------------
(0 rows)

Time: 0.949 ms

When we check the logs for the query plans, I expect them both to only scan
the one child table, however:
< 2017-06-21 13:40:21.086 UTC > LOG:  duration: 0.013 ms  plan:Query Text: SELECT
width_bucket(length(product_title),1, 50, 5) title_length_bucket,        round(avg(review_rating), 2) AS
review_average,       count(*)    FROM       customer_reviews    WHERE        product_group = 'Book'        AND
review_dateBETWEEN start_date AND end_date    GROUP BY        title_length_bucket    ORDER BY
title_length_bucketGroupAggregate (cost=14.95..15.01 rows=2 width=44) (actual 
time=0.011..0.011 rows=0 loops=1)  Group Key: (width_bucket((length(customer_reviews.product_title))::double
precision, '1'::double precision, '50'::double precision, 5))  ->  Sort  (cost=14.95..14.95 rows=2 width=8) (actual
time=0.010..0.010
rows=0 loops=1)        Sort Key:
(width_bucket((length(customer_reviews.product_title))::double precision,
'1'::double precision, '50'::double precision, 5))        Sort Method: quicksort  Memory: 25kB        ->  Result
(cost=0.00..14.94rows=2 width=8) (actual 
time=0.005..0.005 rows=0 loops=1)              ->  Append  (cost=0.00..14.90 rows=2 width=36) (actual
time=0.004..0.004 rows=0 loops=1)                    ->  Seq Scan on customer_reviews  (cost=0.00..0.00
rows=1 width=36) (actual time=0.003..0.003 rows=0 loops=1)                          Filter: ((review_date >=
'2001-01-01'::date)AND 
(review_date <= '2001-12-31'::date) AND (product_group = 'Book'::text))                    ->  Seq Scan on
customer_reviews_2001 
(cost=0.00..14.90 rows=1 width=36) (actual time=0.000..0.000 rows=0
loops=1)                          Filter: ((review_date >= '2001-01-01'::date) AND
(review_date <= '2001-12-31'::date) AND (product_group = 'Book'::text))
< 2017-06-21 13:40:21.086 UTC > CONTEXT:  PL/pgSQL function
title_vs_review_plpgsql(date,date) line 3 at RETURN QUERY
< 2017-06-21 13:40:21.086 UTC > LOG:  duration: 0.640 ms  plan:Query Text: select title_vs_review_plpgsql('2001-01-01',
'2001-12-31');Result (cost=0.00..5.25 rows=1000 width=32) (actual time=0.636..0.636 
rows=0 loops=1)
< 2017-06-21 13:40:26.869 UTC > LOG:  duration: 0.030 ms  plan:Query Text: SELECT
width_bucket(length(product_title),1, 50, 5) title_length_bucket,    round(avg(review_rating), 2) AS review_average,
count(*)FROM  customer_reviewsWHERE    product_group = 'Book'    AND    review_date BETWEEN start_date AND
end_dateGROUPBY    title_length_bucketORDER BY    title_length_bucketGroupAggregate  (cost=74.68..74.88 rows=6
width=44)(actual 
time=0.028..0.028 rows=0 loops=1)  Group Key: (width_bucket((length(customer_reviews.product_title))::double
precision, '1'::double precision, '50'::double precision, 5))  ->  Sort  (cost=74.68..74.70 rows=6 width=8) (actual
time=0.026..0.026
rows=0 loops=1)        Sort Key:
(width_bucket((length(customer_reviews.product_title))::double precision,
'1'::double precision, '50'::double precision, 5))        Sort Method: quicksort  Memory: 25kB        ->  Result
(cost=0.00..74.61rows=6 width=8) (actual 
time=0.007..0.007 rows=0 loops=1)              ->  Append  (cost=0.00..74.50 rows=6 width=36) (actual
time=0.007..0.007 rows=0 loops=1)                    ->  Seq Scan on customer_reviews  (cost=0.00..0.00
rows=1 width=36) (actual time=0.003..0.003 rows=0 loops=1)                          Filter: ((review_date >= $1) AND
(review_date<= 
$2) AND (product_group = 'Book'::text))                    ->  Seq Scan on customer_reviews_2000
(cost=0.00..14.90 rows=1 width=36) (actual time=0.001..0.001 rows=0
loops=1)                          Filter: ((review_date >= $1) AND (review_date <=
$2) AND (product_group = 'Book'::text))                    ->  Seq Scan on customer_reviews_2001
(cost=0.00..14.90 rows=1 width=36) (actual time=0.001..0.001 rows=0
loops=1)                          Filter: ((review_date >= $1) AND (review_date <=
$2) AND (product_group = 'Book'::text))                    ->  Seq Scan on customer_reviews_2002
(cost=0.00..14.90 rows=1 width=36) (actual time=0.000..0.000 rows=0
loops=1)                          Filter: ((review_date >= $1) AND (review_date <=
$2) AND (product_group = 'Book'::text))                    ->  Seq Scan on customer_reviews_2003
(cost=0.00..14.90 rows=1 width=36) (actual time=0.001..0.001 rows=0
loops=1)                          Filter: ((review_date >= $1) AND (review_date <=
$2) AND (product_group = 'Book'::text))                    ->  Seq Scan on customer_reviews_2004
(cost=0.00..14.90 rows=1 width=36) (actual time=0.001..0.001 rows=0
loops=1)                          Filter: ((review_date >= $1) AND (review_date <=
$2) AND (product_group = 'Book'::text))
< 2017-06-21 13:40:26.869 UTC > CONTEXT:  SQL function "title_vs_review_sql"
statement 1
< 2017-06-21 13:40:26.869 UTC > LOG:  duration: 0.684 ms  plan:Query Text: select title_vs_review_sql('2001-01-01',
'2001-12-31');Result (cost=0.00..5.25 rows=1000 width=32) (actual time=0.680..0.680 
rows=0 loops=1)





--
Sent via pgsql-bugs mailing list (pgsql-bugs@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-bugs

Hello,

At Wed, 21 Jun 2017 14:00:56 +0000, cliveevans@ntlworld.com wrote in
<20170621140056.27883.82221@wrigleys.postgresql.org>
> PostgreSQL version: 9.6.3
...
> The same query written using PL/PGSQL will only scan the expected partition
> tables.

The two are different in that the parameters of the PL/PgSQL
function are regarded as constants at the time of planning of the
inner SQL statement, while those of the SQL function are
not. Constraint exclusion is considered while planning so the SQL
function doesn't get benefit of it.

This doesn't seem a bug, but I couldn't find such a description
in the documentation, though. Maybe it should be here.

https://www.postgresql.org/docs/9.6/static/ddl-partitioning.html#DDL-PARTITIONING-CAVEATS

regareds,

-- 
Kyotaro Horiguchi
NTT Open Source Software Center



-- 
Sent via pgsql-bugs mailing list (pgsql-bugs@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-bugs



On 23/06/17 01:39, Kyotaro HORIGUCHI wrote:
Hello,

At Wed, 21 Jun 2017 14:00:56 +0000, cliveevans@ntlworld.com wrote in <20170621140056.27883.82221@wrigleys.postgresql.org>
PostgreSQL version: 9.6.3
...
The same query written using PL/PGSQL will only scan the expected partition
tables.
The two are different in that the parameters of the PL/PgSQL
function are regarded as constants at the time of planning of the
inner SQL statement, while those of the SQL function are
not. Constraint exclusion is considered while planning so the SQL
function doesn't get benefit of it.

I understand this, and I understand why. You can't plan based on what you don't yet know. I'm not entirely clear why the inner query can treat the parameter as a constant, whereas the outer one is forced to treat it as dynamic.

I assume it's something to do with when the plan is created. Possibly it's an attempt to save the planning time and re-use the plan in the case of a 'normal' SQL function, although this seems like a trade off that may well have significant downsides - in this case obviously, but also others.

Perhaps I'm looking for a new feature, rather than reporting a bug. Something that allows lazy planning, where there's a good chance that treating the parameters as constants is likely to improve the produced plan sufficiently to offset the additional overhead of repeatedly planning.

thanks,
--
Clive Evans
On 23/06/17 08:59, Clive Evans wrote:
>
>
> On 23/06/17 01:39, Kyotaro HORIGUCHI wrote:
>> Hello,
>>
>> At Wed, 21 Jun 2017 14:00:56 +0000,cliveevans@ntlworld.com  wrote
in<20170621140056.27883.82221@wrigleys.postgresql.org>
>>> PostgreSQL version: 9.6.3
>> ...
>>> The same query written using PL/PGSQL will only scan the expected partition
>>> tables.
>> The two are different in that the parameters of the PL/PgSQL
>> function are regarded as constants at the time of planning of the
>> inner SQL statement, while those of the SQL function are
>> not. Constraint exclusion is considered while planning so the SQL
>> function doesn't get benefit of it.
>
> I understand this, and I understand why. You can't plan based on what 
> you don't yet know. I'm not entirely clear why the inner query can 
> treat the parameter as a constant, whereas the outer one is forced to 
> treat it as dynamic.
>
> I assume it's something to do with when the plan is created. Possibly 
> it's an attempt to save the planning time and re-use the plan in the 
> case of a 'normal' SQL function, although this seems like a trade off 
> that may well have significant downsides - in this case obviously, but 
> also others.
>
> Perhaps I'm looking for a new feature, rather than reporting a bug. 
> Something that allows lazy planning, where there's a good chance that 
> treating the parameters as constants is likely to improve the produced 
> plan sufficiently to offset the additional overhead of repeatedly 
> planning.

Sorry, mail client went a bit rogue. Here it is again, (hopefully) 
legibly ...

thanks,
Clive


-- 
Sent via pgsql-bugs mailing list (pgsql-bugs@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-bugs