Thread: Constraint exclusion issue

Constraint exclusion issue

From
Mathieu De Zutter
Date:
Hi,

I'm trying to make constraint exclusion work correctly in a query with
only one parameter, but I have some issues.
Please have a look at the scenario below and tell me how I can improve it.

Thanks!


-- I create an inheritance relationship with a check constraint in the child

shs-dev=# create table parent (c char, n integer);
CREATE TABLE
shs-dev=# create table child1 ( ) inherits (parent);
CREATE TABLE
shs-dev=# alter table child1 add check (c = 'a');
ALTER TABLE

-- I query on a row containing both attributes, and pgsql 8.4
correctly skips the child table because of the constraint

shs-dev=# explain select * from parent where (c,n) = ('b',0);
                                                  QUERY PLAN
--------------------------------------------------------------------------------------------------------------
 Result  (cost=0.00..39.10 rows=1 width=12)
   ->  Append  (cost=0.00..39.10 rows=1 width=12)
         ->  Seq Scan on parent  (cost=0.00..39.10 rows=1 width=12)
               Filter: ((c = 'b'::bpchar) AND (n = 0))

-- Ok, lets see if I can parameterize this with only one parameter... NO!

shs-dev=# explain select * from parent where (c,n) = '("b",0)';
ERROR:  input of anonymous composite types is not implemented

-- I create a type so it's not anonymous anymore

shs-dev=# create type y as (c char, n integer);
CREATE TYPE

-- But pgsql forgets about the constraint now :(

shs-dev=# explain select * from parent where (c,n)::y = '("b",0)'::y;
                                                      QUERY PLAN
----------------------------------------------------------------------------------------------------------------------
 Result  (cost=0.00..78.20 rows=20 width=12)
   ->  Append  (cost=0.00..78.20 rows=20 width=12)
         ->  Seq Scan on parent  (cost=0.00..39.10 rows=10 width=12)
               Filter: (ROW(c, n)::y = '(b,0)'::y)
         ->  Seq Scan on child1 parent  (cost=0.00..39.10 rows=10 width=12)
               Filter: (ROW(c, n)::y = '(b,0)'::y)


-- This is OK (but has two parameters, I want only one)

shs-dev=# explain select * from parent where ((c,n)::y).c = 'b' and
((c,n)::y).n = 0;
                                                  QUERY PLAN
--------------------------------------------------------------------------------------------------------------
 Result  (cost=0.00..39.10 rows=1 width=12)
   ->  Append  (cost=0.00..39.10 rows=1 width=12)
         ->  Seq Scan on parent  (cost=0.00..39.10 rows=1 width=12)
               Filter: ((c = 'b'::bpchar) AND (n = 0))


-- This isn't OK

shs-dev=# explain select * from parent where ((c,n)::y).c =
('("b",0)'::y).c and ((c,n)::y).n = ('("b",0)'::y).n;
                                                     QUERY PLAN
---------------------------------------------------------------------------------------------------------------------
 Result  (cost=0.00..78.20 rows=2 width=12)
   ->  Append  (cost=0.00..78.20 rows=2 width=12)
         ->  Seq Scan on parent  (cost=0.00..39.10 rows=1 width=12)
               Filter: ((c = ('(b,0)'::y).c) AND (n = ('(b,0)'::y).n))
         ->  Seq Scan on child1 parent  (cost=0.00..39.10 rows=1 width=12)
               Filter: ((c = ('(b,0)'::y).c) AND (n = ('(b,0)'::y).n))


-- So the problem seems to be that the 'b' value cannot be deduced in
time for the constraint exclusion to do its job.


--
Regards,
Mathieu

Re: Constraint exclusion issue

From
Scott Marlowe
Date:
On Sat, Jan 16, 2010 at 11:02 AM, Mathieu De Zutter
<mathieu@dezutter.org> wrote:
> Hi,
>
> I'm trying to make constraint exclusion work correctly in a query with
> only one parameter, but I have some issues.
> Please have a look at the scenario below and tell me how I can improve it.
>
> Thanks!
>
>
> -- I create an inheritance relationship with a check constraint in the child
>
> shs-dev=# create table parent (c char, n integer);
> CREATE TABLE
> shs-dev=# create table child1 ( ) inherits (parent);
> CREATE TABLE
> shs-dev=# alter table child1 add check (c = 'a');
> ALTER TABLE
>
> -- I query on a row containing both attributes, and pgsql 8.4
> correctly skips the child table because of the constraint
>
> shs-dev=# explain select * from parent where (c,n) = ('b',0);
>                                                  QUERY PLAN
> --------------------------------------------------------------------------------------------------------------
>  Result  (cost=0.00..39.10 rows=1 width=12)
>   ->  Append  (cost=0.00..39.10 rows=1 width=12)
>         ->  Seq Scan on parent  (cost=0.00..39.10 rows=1 width=12)
>               Filter: ((c = 'b'::bpchar) AND (n = 0))
>
> -- Ok, lets see if I can parameterize this with only one parameter... NO!
>
> shs-dev=# explain select * from parent where (c,n) = '("b",0)';
> ERROR:  input of anonymous composite types is not implemented

Shouldn't that be 'b' not "b" ?

Re: Constraint exclusion issue

From
Mathieu De Zutter
Date:
On Sat, Jan 16, 2010 at 7:26 PM, Scott Marlowe <scott.marlowe@gmail.com> wrote:
> On Sat, Jan 16, 2010 at 11:02 AM, Mathieu De Zutter
> <mathieu@dezutter.org> wrote:
>> shs-dev=# explain select * from parent where (c,n) = '("b",0)';
>> ERROR:  input of anonymous composite types is not implemented
>
> Shouldn't that be 'b' not "b" ?

It is special syntax to avoid escapes:

shs-dev=# select '("b",0)'::y;
   y
-------
 (b,0)

shs-dev=# select ('("b,",0)'::y).c;
ERROR:  value too long for type character(1)
LINE 1: select ('("b,",0)'::y).c;

Re: Constraint exclusion issue

From
Emanuel Calvo Franco
Date:
2010/1/16 Mathieu De Zutter <mathieu@dezutter.org>:
> On Sat, Jan 16, 2010 at 7:26 PM, Scott Marlowe <scott.marlowe@gmail.com> wrote:
>> On Sat, Jan 16, 2010 at 11:02 AM, Mathieu De Zutter
>> <mathieu@dezutter.org> wrote:
>>> shs-dev=# explain select * from parent where (c,n) = '("b",0)';
>>> ERROR:  input of anonymous composite types is not implemented
>>
>> Shouldn't that be 'b' not "b" ?
>
> It is special syntax to avoid escapes:
>
> shs-dev=# select '("b",0)'::y;
>   y
> -------
>  (b,0)
>

To avoid escapes use E'string'.

In that case the ' will be include into the string, so it will be not more
1 char lenght.

Or something like this:
select ('(' || quote_literal('b') || ',0)')::y

> shs-dev=# select ('("b,",0)'::y).c;
> ERROR:  value too long for type character(1)
> LINE 1: select ('("b,",0)'::y).c;
>
> --


--
              Emanuel Calvo Franco
             DBA at:  www.siu.edu.ar
        www.emanuelcalvofranco.com.ar
       Join: http://www.thevenusproject.com/

Re: Constraint exclusion issue

From
Jeff Davis
Date:
On Sat, 2010-01-16 at 19:02 +0100, Mathieu De Zutter wrote:
> Hi,
>
> I'm trying to make constraint exclusion work correctly in a query with
> only one parameter, but I have some issues.
> Please have a look at the scenario below and tell me how I can improve it.
>

...

> shs-dev=# explain select * from parent where (c,n) = ('b',0);

...

> -- Ok, lets see if I can parameterize this with only one parameter... NO!

Is there a specific reason you need to parameterize it with only one
parameter? Or is it just the way you would prefer to write it?

> shs-dev=# explain select * from parent where (c,n)::y = '("b",0)'::y;

You are just expecting the optimizer to be smarter than it is. In order
to determine that the predicate above is incompatible with the CHECK
constraint on child1, the optimizer would have to decompose the record,
then translate it internally into a form where the contradiction is
apparent.

If I were to take a guess, the reason that the first form worked is that
the records were never actually formed, the predicate was rewritten into
"c = 'b' AND n = 0".

Regards,
    Jeff Davis


Re: Constraint exclusion issue

From
Mathieu De Zutter
Date:
On Sun, Jan 17, 2010 at 8:06 PM, Jeff Davis <pgsql@j-davis.com> wrote:
> On Sat, 2010-01-16 at 19:02 +0100, Mathieu De Zutter wrote:
>> Hi,
>>
>> I'm trying to make constraint exclusion work correctly in a query with
>> only one parameter, but I have some issues.
>> Please have a look at the scenario below and tell me how I can improve it.
>>
>
> ...
>
>> shs-dev=# explain select * from parent where (c,n) = ('b',0);
>
> ...
>
>> -- Ok, lets see if I can parameterize this with only one parameter... NO!
>
> Is there a specific reason you need to parameterize it with only one
> parameter? Or is it just the way you would prefer to write it?

It would be simpler for my code because it would reduce the number of
parameters in a lot of queries. The queries themselves could be
shorted too. However, I can do without.

>> shs-dev=# explain select * from parent where (c,n)::y = '("b",0)'::y;
>
> You are just expecting the optimizer to be smarter than it is. In order
> to determine that the predicate above is incompatible with the CHECK
> constraint on child1, the optimizer would have to decompose the record,
> then translate it internally into a form where the contradiction is
> apparent.
>
> If I were to take a guess, the reason that the first form worked is that
> the records were never actually formed, the predicate was rewritten into
> "c = 'b' AND n = 0".

Can you explain this then:
OK: select * from parent where (c,n) = ('b',0);
NOT OK: select * from parent where (c,n)::y = ('b',0)::y;

Adding the row type makes it unaware of the contradiction. The strange
thing is that both queries compare with records with the same types.
The only difference is that the first uses an anonymous record type
and the second does not.

Kind regards,

Mathieu

Re: Constraint exclusion issue

From
Jeff Davis
Date:
On Sun, 2010-01-17 at 21:48 +0100, Mathieu De Zutter wrote:
> Can you explain this then:
> OK: select * from parent where (c,n) = ('b',0);
> NOT OK: select * from parent where (c,n)::y = ('b',0)::y;

Once you pass (c,n) into the cast, you get out something new that's
neither c nor n. It's as if you said "n1 + n2 = 5 + 3".

You are able to recognize that a record type is special, because you can
get the original components even after the transformation (unlike +,
which is irreversible). That allows you to transform the predicate
"(c,n)::y = ('b',0)::y" into an equivalent** form "c = 'b' AND n = 0",
which makes the contradiction with the CHECK constraint apparent.

The optimizer isn't that smart though. You're following a chain of
reasoning, and usually optimizers only go so far, because that can get
expensive, quickly.

** That's actually not really equivalent in the general case, anyway,
because of NULLs. "('a', NULL)::y = ('a', NULL)::y" is TRUE, but "'a' =
'a' AND NULL = NULL" is NULL. Interestingly, "('a', NULL) = ('a', NULL)"
is NULL as well (which might be a bug). So there isn't some nice
canonical form for "record = record" that will solve all of these
problems, which makes the optimization problem a lot harder.

Regards,
    Jeff Davis