Thread: Check/unique constraint question

Check/unique constraint question

From
Jeff Frost
Date:
I have a table with the following structure:
   Column   |  Type   |       Modifiers
------------+---------+----------------------- active     | boolean | not null default true id         | integer | not
null
(other columns left out)

And would like to make a unique constraint which would only check the 
uniqueness of id if active=true.

So, the following values would be acceptable:

('f',5)
('f',5)
('t',5)

But these would not be:

('t',5)
('t',5)

Basically, I want something like:
ALTER TABLE bar ADD CONSTRAINT foo UNIQUE(active (where active='t'),id)

But the above does not appear to exist.  Is there a simple way to create a 
check constraint for this type of situation, or do I need to create a function 
to eval a check constraint?

-- 
Jeff Frost, Owner     <jeff@frostconsultingllc.com>
Frost Consulting, LLC     http://www.frostconsultingllc.com/
Phone: 650-780-7908    FAX: 650-649-1954


Re: Check/unique constraint question

From
"Nikolay Samokhvalov"
Date:
Unfortunately, at the moment Postgres doesn't support subqueries in
CHECK constraints, so it's seems that you should use trigger to check
what you need, smth like this:

CREATE OR REPLACE FUNCTION foo_check() RETURNS trigger AS $BODY$
BEGIN   IF NEW.active = TRUE AND NEW.id IN (       SELECT id FROM foo WHERE active = TRUE AND id = NEW.id   ) THEN
RAISE EXCEPTION 'Uniqueness violation on column id (%)', NEW.id;   END IF; 
   RETURN NEW;
END
$BODY$  LANGUAGE plpgsql;

CREATE TRIGGER foo_check BEFORE INSERT OR UPDATE ON foo   FOR EACH ROW EXECUTE PROCEDURE foo_check();

On 3/5/06, Jeff Frost <jeff@frostconsultingllc.com> wrote:
> I have a table with the following structure:
>
>     Column   |  Type   |       Modifiers
> ------------+---------+-----------------------
>   active     | boolean | not null default true
>   id         | integer | not null
> (other columns left out)
>
> And would like to make a unique constraint which would only check the
> uniqueness of id if active=true.
>
> So, the following values would be acceptable:
>
> ('f',5)
> ('f',5)
> ('t',5)
>
> But these would not be:
>
> ('t',5)
> ('t',5)
>
> Basically, I want something like:
> ALTER TABLE bar ADD CONSTRAINT foo UNIQUE(active (where active='t'),id)
>
> But the above does not appear to exist.  Is there a simple way to create a
> check constraint for this type of situation, or do I need to create a function
> to eval a check constraint?
>
> --
> Jeff Frost, Owner       <jeff@frostconsultingllc.com>
> Frost Consulting, LLC   http://www.frostconsultingllc.com/
> Phone: 650-780-7908     FAX: 650-649-1954
>
> ---------------------------(end of broadcast)---------------------------
> TIP 5: don't forget to increase your free space map settings
>


--
Best regards,
Nikolay

Re: Check/unique constraint question

From
"Nikolay Samokhvalov"
Date:
just a better way (workaround for subqueries in check constraints...):

CREATE OR REPLACE FUNCTION id_is_valid(   val INTEGER
) RETURNS boolean AS $BODY$
BEGIN   IF val IN (       SELECT id FROM foo WHERE active = TRUE AND id = val   ) THEN       RETURN FALSE;   ELSE
RETURNTRUE;   END IF; 
END
$BODY$  LANGUAGE plpgsql;
ALTER TABLE foo ADD CONSTRAINT C_foo_iniq_if_true CHECK (active =
FALSE OR id_is_valid(id));

On 3/5/06, Nikolay Samokhvalov <samokhvalov@gmail.com> wrote:
> Unfortunately, at the moment Postgres doesn't support subqueries in
> CHECK constraints, so it's seems that you should use trigger to check
> what you need, smth like this:
>
> CREATE OR REPLACE FUNCTION foo_check() RETURNS trigger AS $BODY$
> BEGIN
>     IF NEW.active = TRUE AND NEW.id IN (
>         SELECT id FROM foo WHERE active = TRUE AND id = NEW.id
>     ) THEN
>         RAISE EXCEPTION 'Uniqueness violation on column id (%)', NEW.id;
>     END IF;
>
>     RETURN NEW;
> END
> $BODY$  LANGUAGE plpgsql;
>
> CREATE TRIGGER foo_check BEFORE INSERT OR UPDATE ON foo
>     FOR EACH ROW EXECUTE PROCEDURE foo_check();
>
> On 3/5/06, Jeff Frost <jeff@frostconsultingllc.com> wrote:
> > I have a table with the following structure:
> >
> >     Column   |  Type   |       Modifiers
> > ------------+---------+-----------------------
> >   active     | boolean | not null default true
> >   id         | integer | not null
> > (other columns left out)
> >
> > And would like to make a unique constraint which would only check the
> > uniqueness of id if active=true.
> >
> > So, the following values would be acceptable:
> >
> > ('f',5)
> > ('f',5)
> > ('t',5)
> >
> > But these would not be:
> >
> > ('t',5)
> > ('t',5)
> >
> > Basically, I want something like:
> > ALTER TABLE bar ADD CONSTRAINT foo UNIQUE(active (where active='t'),id)
> >
> > But the above does not appear to exist.  Is there a simple way to create a
> > check constraint for this type of situation, or do I need to create a function
> > to eval a check constraint?
> >
> > --
> > Jeff Frost, Owner       <jeff@frostconsultingllc.com>
> > Frost Consulting, LLC   http://www.frostconsultingllc.com/
> > Phone: 650-780-7908     FAX: 650-649-1954
> >
> > ---------------------------(end of broadcast)---------------------------
> > TIP 5: don't forget to increase your free space map settings
> >
>
>
> --
> Best regards,
> Nikolay
>


--
Best regards,
Nikolay

Re: Check/unique constraint question

From
Volkan YAZICI
Date:
On Mar 05 12:02, Nikolay Samokhvalov wrote:
> Unfortunately, at the moment Postgres doesn't support subqueries in
> CHECK constraints

I don't know how feasible this is but, it's possible to hide subqueries
that will be used in constraints in procedures. Here's an alternative
method to Nikolay's:

CREATE TABLE where_check (active bool, id int);

CREATE OR REPLACE FUNCTION check_id (bool, int) RETURNS bool AS ' SELECT CASE   WHEN $1 THEN NOT EXISTS (SELECT 1
                    FROM where_check AS W                            WHERE W.active IS TRUE AND W.id = $2)   ELSE TRUE
END;
' LANGUAGE SQL;

-- A partial index like
-- CREATE INDEX active_id_idx ON where_check (id)
--   WHERE active IS TRUE;
-- should speed up above query

ALTER TABLE where_check ADD CONSTRAINT idchk CHECK (check_id(active, id));

test=# INSERT INTO where_check VALUES (TRUE, 2);
INSERT 0 1
test=# INSERT INTO where_check VALUES (FALSE, 2);
INSERT 0 1
test=# INSERT INTO where_check VALUES (TRUE, 2);
ERROR:  new row for relation "where_check" violates check constraint
"idchk"


Regards.


Re: Check/unique constraint question

From
Michael Glaesemann
Date:
On Mar 5, 2006, at 17:25 , Jeff Frost wrote:

> And would like to make a unique constraint which would only check  
> the uniqueness of id if active=true.

I believe you're looking for what is called a partial index.

http://www.postgresql.org/docs/current/interactive/indexes-partial.html

Note, I've added a foo_id column to make sure each row is unique.  
(Duplicates are a Bad Thing.)

create table foo
(    foo_id serial not null    , id integer not null    , active boolean not null

);

create unique index foo_partial_idx on foo (id) where active;

insert into foo (id, active) values (5, false);
insert into foo (id, active) values (5, false);
insert into foo (id, active) values (5, true);
insert into foo (id, active) values (6, false);
insert into foo (id, active) values (6, true);

select * from foo;

foo_id | id | active
--------+----+--------      1 |  5 | f      2 |  5 | f      3 |  5 | t      4 |  6 | f      5 |  6 | t
(5 rows)

insert into foo (id, active) values (5, true);
ERROR:  duplicate key violates unique constraint "foo_partial_idx"

Michael Glaesemann
grzm myrealbox com





Re: Check/unique constraint question

From
Karsten Hilbert
Date:
On Sun, Mar 05, 2006 at 12:02:58PM +0300, Nikolay Samokhvalov wrote:

> Unfortunately, at the moment Postgres doesn't support subqueries in
> CHECK constraints, so it's seems that you should use trigger to check
> what you need
The OP could also use a check constraint with a function if
everything (the context) but the value to check is known
beforehand or can be derived from it.

Karsten
-- 
GPG key ID E4071346 @ wwwkeys.pgp.net
E167 67FD A291 2BEA 73BD  4537 78B9 A9F9 E407 1346


Re: Check/unique constraint question

From
Jeff Frost
Date:
On Sun, 5 Mar 2006, Michael Glaesemann wrote:

>
> On Mar 5, 2006, at 17:25 , Jeff Frost wrote:
>
> I believe you're looking for what is called a partial index.
>
> http://www.postgresql.org/docs/current/interactive/indexes-partial.html
>
> create unique index foo_partial_idx on foo (id) where active;

Thanks Michael, that's exactly what I was looking for.  Guess I just couldn't 
figure out how to search for it in the docs properly.

Also thanks to Nikolay and Volkan for the other responses which were along the 
lines of what I thought was necessary.

-- 
Jeff Frost, Owner     <jeff@frostconsultingllc.com>
Frost Consulting, LLC     http://www.frostconsultingllc.com/
Phone: 650-780-7908    FAX: 650-649-1954


Re: Check/unique constraint question

From
Scott Rohde
Date:
There is something a bit odd about this solution: If you start with an empty
table, the constraint will allow you to do
   INSERT INTO foo (active, id) VALUES ('t', 5);

But if you insert this row into the table first and /then/ try to add the
constraint, it will complain that an existing row violates the constraint.

This begs the question of when constraints are checked.

I had always thought of constraints as being static conditions that (unlike
some trigger condition that masquerades as a constraint) apply equally to
existing rows and to rows you are about to add.  This seems to show that not
all constraints work this way.




Nikolay Samokhvalov wrote
> just a better way (workaround for subqueries in check constraints...):
> 
> CREATE OR REPLACE FUNCTION id_is_valid(
>     val INTEGER
> ) RETURNS boolean AS $BODY$
> BEGIN
>     IF val IN (
>         SELECT id FROM foo WHERE active = TRUE AND id = val
>     ) THEN
>         RETURN FALSE;
>     ELSE
>         RETURN TRUE;
>     END IF;
> END
> $BODY$  LANGUAGE plpgsql;
> ALTER TABLE foo ADD CONSTRAINT C_foo_iniq_if_true CHECK (active =
> FALSE OR id_is_valid(id));
> 
> ...





--
View this message in context: http://postgresql.nabble.com/Check-unique-constraint-question-tp2145289p5829778.html
Sent from the PostgreSQL - sql mailing list archive at Nabble.com.



Re: Check/unique constraint question

From
Tom Lane
Date:
Scott Rohde <srohde@illinois.edu> writes:
> There is something a bit odd about this solution: If you start with an empty
> table, the constraint will allow you to do

>     INSERT INTO foo (active, id) VALUES ('t', 5);

> But if you insert this row into the table first and /then/ try to add the
> constraint, it will complain that an existing row violates the constraint.

> This begs the question of when constraints are checked.

> I had always thought of constraints as being static conditions that (unlike
> some trigger condition that masquerades as a constraint) apply equally to
> existing rows and to rows you are about to add.  This seems to show that not
> all constraints work this way.

Indeed, this illustrates perfectly why subqueries in CHECK constraints
are generally a Bad Idea: the constraint is no longer just about the
contents of one row but about its relationship to other rows, and that
makes the timing of checks relevant.  Hiding the subquery in a function
doesn't do anything to resolve that fundamental issue.

The original example seemed to work for retail inserts because the check
gets applied before the row is physically inserted.  It would fail on
updates though, or when trying to add the constraint after the fact.
        regards, tom lane



Re: Check/unique constraint question

From
Scott Rohde
Date:
Tom Lane-2 wrote
> ...
> Indeed, this illustrates perfectly why subqueries in CHECK constraints
> are generally a Bad Idea: the constraint is no longer just about the
> contents of one row but about its relationship to other rows, and that
> makes the timing of checks relevant.  Hiding the subquery in a function
> doesn't do anything to resolve that fundamental issue.
> ...

I don't think subqueries in CHECK constraints are a bad idea /per se/--to my
mind it would depend on how they actually work.  I don't know enough about
the SQL standard or about products that support them to know if they work
the way I /think/ they should work, which is basically this: "Guarantee that
condition X (written as a constraint on table Y) is satisfied by the
database when (1) the constraint is first added, and (2) whenever a change
is made to one or more rows of table Y."

In our example, if the function in the CHECK constraint was run after
provisionally changing the foo table, and if the changes were rolled back if
and only if the check failed, then it seems there would be no problem.  It
may well be that bona fide CHECK subqueries (as opposed to procedural
functions run by the CHECK constraint) /do/ work this way.

In PostgreSQL, the best approximation I could come up with was a combination
of the TRIGGER function mentioned in Nikolay's first post together with this
CHECK:

CREATE OR REPLACE FUNCTION id_is_valid(    val INTEGER 
) RETURNS boolean AS $BODY$   DECLARE id_is_unique boolean;
BEGIN    SELECT COUNT(*) <= 1 FROM foo WHERE active = TRUE AND id = val INTO
id_is_unique;   RETURN id_is_unique;
END 
$BODY$  LANGUAGE plpgsql;

ALTER TABLE foo ADD CONSTRAINT C_foo_iniq_if_true CHECK (id_is_valid(id)); 



To summarize, any constraint should be fine as long as (1) it is always run
when any change is made to the database that might affect its value; (2) it
is run on the (provisional) /result/ state of the database.





--
View this message in context: http://postgresql.nabble.com/Check-unique-constraint-question-tp2145289p5829820.html
Sent from the PostgreSQL - sql mailing list archive at Nabble.com.



Re: Check/unique constraint question

From
Tom Lane
Date:
Scott Rohde <srohde@illinois.edu> writes:
> Tom Lane-2 wrote
>> Indeed, this illustrates perfectly why subqueries in CHECK constraints
>> are generally a Bad Idea: the constraint is no longer just about the
>> contents of one row but about its relationship to other rows, and that
>> makes the timing of checks relevant.  Hiding the subquery in a function
>> doesn't do anything to resolve that fundamental issue.

> I don't think subqueries in CHECK constraints are a bad idea /per se/--to my
> mind it would depend on how they actually work.  I don't know enough about
> the SQL standard or about products that support them to know if they work
> the way I /think/ they should work, which is basically this: "Guarantee that
> condition X (written as a constraint on table Y) is satisfied by the
> database when (1) the constraint is first added, and (2) whenever a change
> is made to one or more rows of table Y."

They certainly don't work like that in Postgres, and I doubt in other
DBMSes either.  A CHECK constraint is assumed to involve only the contents
of a single row, and it's checked for each row when (actually before) that
row is inserted or updated.

There is a thing in SQL called an "assertion" which has the sort of
unconstrained semantics you imagine.  Postgres doesn't implement those,
and we're not alone.  The cost of enforcing them is nigh prohibitive.
        regards, tom lane