Thread: Alternative to Select in table check constraint

Alternative to Select in table check constraint

From
Richard Broersma Jr
Date:
I am practicing with SQL examples comming from the book:

JOE CELKO'S
SQL
PUZZLES
& ANSWERS

The following codes doesn't work on PostgreSQL 8.1.4 but according to the book does conform to
SQL-92.  Is there any other solutions that would result in the same effect?  Or is this an example
of a contraint that should be avoided at all costs?

CREATE TABLE BADGES
(       BADGENO         SERIAL NOT NULL PRIMARY KEY,       EMPNO           INTEGER NOT NULL REFERENCES SECEMPLOYEES
(EMPNO),      ISSUEDATE       DATE NOT NULL,       STATUS          CHAR(1) NOT NULL CHECK ( STATUS IN ('A', 'I')),
 
       CHECK   ( 1 = ALL (     SELECT COUNT(STATUS)                               FROM BADGES
   WHERE STATUS = 'A'                               GROUP BY EMPNO))
 
);



Re: Alternative to Select in table check constraint

From
Michael Glaesemann
Date:
On Jul 1, 2006, at 6:55 , Richard Broersma Jr wrote:

>         CHECK   ( 1 = ALL (     SELECT COUNT(STATUS)
>                                 FROM BADGES
>                                 WHERE STATUS = 'A'
>                                 GROUP BY EMPNO))
From the PostgreSQL docs (http://www.postgresql.org/docs/8.1/ 
interactive/sql-createtable.html)

> Currently, CHECK expressions cannot contain subqueries nor refer to  
> variables other than columns of the current row.

This is why the the above won't work. You can probably do something  
similar by creating UNIQUE index with a WHERE clause. For example (if  
I'm understanding the intent),

CREATE UNIQUE INDEX one_a_badge_per_employee_idx
ON badges (empno)
WHERE status = 'A';

Here are links to more documentation on indexes:

http://www.postgresql.org/docs/8.1/interactive/sql-createindex.html
http://www.postgresql.org/docs/8.1/interactive/indexes-partial.html

Hope this helps.

Michael Glaesemann
grzm seespotcode net





Re: Alternative to Select in table check constraint

From
Richard Broersma Jr
Date:
> >         CHECK   ( 1 = ALL (     SELECT COUNT(STATUS)
> >                                 FROM BADGES
> >                                 WHERE STATUS = 'A'
> >                                 GROUP BY EMPNO))
> 
>  From the PostgreSQL docs (http://www.postgresql.org/docs/8.1/ 
> interactive/sql-createtable.html)
> 
> CREATE UNIQUE INDEX one_a_badge_per_employee_idx
> ON badges (empno)
> WHERE status = 'A';
> http://www.postgresql.org/docs/8.1/interactive/sql-createindex.html
> http://www.postgresql.org/docs/8.1/interactive/indexes-partial.html

Michael,

Partial indexs seem to be "what the doctor ordered!"   And your suggest is right on, the idea of
the constraint is to allow only one active badge status at a time.

But now that I think about it, using the authors suggestion (if it actually worked), how would
would it be possible to change the active status from one badge to another?

Oh well, partial index are obvious the superior solution since the entire table doesn't not have
to be scanned to determine if the new badge can be set to active.

Once again thanks for the insight.

Regards,

Richard Broersma Jr.


Re: Alternative to Select in table check constraint

From
Tom Lane
Date:
Richard Broersma Jr <rabroersma@yahoo.com> writes:
> The following codes doesn't work on PostgreSQL 8.1.4 but according to
> the book does conform to SQL-92.

>         CHECK   ( 1 = ALL (     SELECT COUNT(STATUS)
>                                 FROM BADGES
>                                 WHERE STATUS = 'A'
>                                 GROUP BY EMPNO))

Yeah, the spec does allow sub-SELECTs in CHECK constraints but PG
doesn't implement that.  The problem with it is that there's no clear
way to make it perform reasonably, because the CHECK doesn't simply
implicate the row you're currently inserting/updating --- every other
row is potentially referenced by the sub-SELECT, and so changing row
X might make the CHECK condition fail at row Y.  A brute-force
implementation would be that every update of any sort to BADGES causes
us to re-evaluate the CHECK constraint (hence, re-scan BADGES entirely
to compute the sub-SELECT) for every row of BADGES (hence, O(N^2) work
if there are N rows in the table).  That is certainly unworkable :-(.
A bright person can think of ways to optimize particular cases but
it's not easy to see how the machine might figure it out for arbitrary
SELECTs.

The unique-index hack that Michael suggested amounts to hand-optimizing
the sub-SELECT constraint into something that's efficiently checkable.
        regards, tom lane


Re: Alternative to Select in table check constraint

From
Richard Broersma Jr
Date:
> > The following codes doesn't work on PostgreSQL 8.1.4 but according to
> > the book does conform to SQL-92.
> 
> >         CHECK   ( 1 = ALL (     SELECT COUNT(STATUS)
> >                                 FROM BADGES
> >                                 WHERE STATUS = 'A'
> >                                 GROUP BY EMPNO))
> 
> Yeah, the spec does allow sub-SELECTs in CHECK constraints but PG
> doesn't implement that.  The problem with it is that there's no clear
> way to make it perform reasonably, because the CHECK doesn't simply
> implicate the row you're currently inserting/updating --- every other
> row is potentially referenced by the sub-SELECT, and so changing row
> X might make the CHECK condition fail at row Y.  A brute-force
> implementation would be that every update of any sort to BADGES causes
> us to re-evaluate the CHECK constraint (hence, re-scan BADGES entirely
> to compute the sub-SELECT) for every row of BADGES (hence, O(N^2) work
> if there are N rows in the table).  That is certainly unworkable :-(.
> A bright person can think of ways to optimize particular cases but
> it's not easy to see how the machine might figure it out for arbitrary
> SELECTs.
> 
> The unique-index hack that Michael suggested amounts to hand-optimizing
> the sub-SELECT constraint into something that's efficiently checkable.
> 
>             regards, tom lane
Ah.  Thanks for the clarification.

Regards,

Richard Broersma Jr.


Re: Alternative to Select in table check constraint

From
Erik Jones
Date:
Richard Broersma Jr wrote:
> But now that I think about it, using the authors suggestion (if it actually worked), how would
> would it be possible to change the active status from one badge to another?
>   
Well, the check constraint as you wrote it requires that there always be 
a badge listed as active for each employee.  If you changed
the contstraint to: CHECK  ( 2 > .....) then you'd be able to unset the 
active status and then set a new one to active.

-- 
erik jones <erik@myemma.com>
software development
emma(r)



Re: Alternative to Select in table check constraint

From
Rod Taylor
Date:
On Fri, 2006-06-30 at 18:41 -0700, Richard Broersma Jr wrote:
> > >         CHECK   ( 1 = ALL (     SELECT COUNT(STATUS)
> > >                                 FROM BADGES
> > >                                 WHERE STATUS = 'A'
> > >                                 GROUP BY EMPNO))
> > 
> >  From the PostgreSQL docs (http://www.postgresql.org/docs/8.1/ 
> > interactive/sql-createtable.html)
> > 
> > CREATE UNIQUE INDEX one_a_badge_per_employee_idx
> > ON badges (empno)
> > WHERE status = 'A';
> > http://www.postgresql.org/docs/8.1/interactive/sql-createindex.html
> > http://www.postgresql.org/docs/8.1/interactive/indexes-partial.html
> 
> Michael,
> 
> Partial indexs seem to be "what the doctor ordered!"   And your suggest is right on, the idea of
> the constraint is to allow only one active badge status at a time.
> 
> But now that I think about it, using the authors suggestion (if it actually worked), how would
> would it be possible to change the active status from one badge to another?

Unset the status first then set on the new one. Same transaction of
course.

You may find this type of constraint is more workable with a TRIGGER
deferred until commit time than a unique constraint which cannot (at
this time in PostgreSQL) be deferred.
-- 



Re: Alternative to Select in table check constraint

From
"Aaron Bono"
Date:
This is more of an implementation option, but when I worry about what is active/inactive I put start/end dates on the tables.  Then you don't need active indicators.  You just look for the record where now() is >= start date and now() <= end date or end date is null.  You can even activate/deactivate a badge on a future date.  Of course, this complicates the data integrity - you will need some kind of specialized trigger that checks the data and makes sure there are no date overlaps to ensure you don't have two badges active at the same time.  But is also gives you a history of badges and their activities.

-Aaron

On 6/30/06, Richard Broersma Jr <rabroersma@yahoo.com> wrote:
> >         CHECK   ( 1 = ALL (     SELECT COUNT(STATUS)
> >                                 FROM BADGES
> >                                 WHERE STATUS = 'A'
> >                                 GROUP BY EMPNO))
>
>  From the PostgreSQL docs (http://www.postgresql.org/docs/8.1/
> interactive/sql-createtable.html)
>
> CREATE UNIQUE INDEX one_a_badge_per_employee_idx
> ON badges (empno)
> WHERE status = 'A';
> http://www.postgresql.org/docs/8.1/interactive/sql-createindex.html
> http://www.postgresql.org/docs/8.1/interactive/indexes-partial.html

Michael,

Partial indexs seem to be "what the doctor ordered!"   And your suggest is right on, the idea of
the constraint is to allow only one active badge status at a time.

But now that I think about it, using the authors suggestion (if it actually worked), how would
would it be possible to change the active status from one badge to another?

Oh well, partial index are obvious the superior solution since the entire table doesn't not have
to be scanned to determine if the new badge can be set to active.

Once again thanks for the insight.

Regards,

Richard Broersma Jr.

Re: Alternative to Select in table check constraint

From
Richard Broersma Jr
Date:
> On Fri, 2006-06-30 at 18:41 -0700, Richard Broersma Jr wrote:
> > > >         CHECK   ( 1 = ALL (     SELECT COUNT(STATUS)
> > > >                                 FROM BADGES
> > > >                                 WHERE STATUS = 'A'
> > > >                                 GROUP BY EMPNO))
> > > 
> > >  From the PostgreSQL docs (http://www.postgresql.org/docs/8.1/ 
> > > interactive/sql-createtable.html)
> > > 
> > > CREATE UNIQUE INDEX one_a_badge_per_employee_idx
> > > ON badges (empno)
> > > WHERE status = 'A';
> > > http://www.postgresql.org/docs/8.1/interactive/sql-createindex.html
> > > http://www.postgresql.org/docs/8.1/interactive/indexes-partial.html
> > 
> > Michael,
> > 
> > Partial indexs seem to be "what the doctor ordered!"   And your suggest is right on, the idea
> of
> > the constraint is to allow only one active badge status at a time.
> > 
> > But now that I think about it, using the authors suggestion (if it actually worked), how would
> > would it be possible to change the active status from one badge to another?
> 
> Unset the status first then set on the new one. Same transaction of
> course.
> 
> You may find this type of constraint is more workable with a TRIGGER
> deferred until commit time than a unique constraint which cannot (at
> this time in PostgreSQL) be deferred.

Thanks for the Input Rod.  I will try implementing a trigger as a way to constrain the input data
to see how it works.

Regards,

Richard Broersma Jr.


Re: Alternative to Select in table check constraint

From
Richard Broersma Jr
Date:
> This is more of an implementation option, but when I worry about what is
> active/inactive I put start/end dates on the tables.  Then you don't need
> active indicators.  You just look for the record where now() is >= start
> date and now() <= end date or end date is null.  You can even
> activate/deactivate a badge on a future date.  Of course, this complicates
> the data integrity - you will need some kind of specialized trigger that
> checks the data and makes sure there are no date overlaps to ensure you
> don't have two badges active at the same time.  But is also gives you a
> history of badges and their activities.

Good point. I take it that this type of solution stems from temporal schema design.

Regards,

Richard Broersma Jr.



Re: Alternative to Select in table check constraint

From
Andreas Joseph Krogh
Date:
On Saturday 01 July 2006 03:41, Richard Broersma Jr wrote:
> > >         CHECK   ( 1 = ALL (     SELECT COUNT(STATUS)
> > >                                 FROM BADGES
> > >                                 WHERE STATUS = 'A'
> > >                                 GROUP BY EMPNO))
> >
> >  From the PostgreSQL docs (http://www.postgresql.org/docs/8.1/
> > interactive/sql-createtable.html)
> >
> > CREATE UNIQUE INDEX one_a_badge_per_employee_idx
> > ON badges (empno)
> > WHERE status = 'A';
> > http://www.postgresql.org/docs/8.1/interactive/sql-createindex.html
> > http://www.postgresql.org/docs/8.1/interactive/indexes-partial.html
>
> Michael,
>
> Partial indexs seem to be "what the doctor ordered!"   And your suggest is
> right on, the idea of the constraint is to allow only one active badge
> status at a time.
>
> But now that I think about it, using the authors suggestion (if it actually
> worked), how would would it be possible to change the active status from
> one badge to another?

First set status='I' on the one that has status='A', and set status='A' on the
one you want.

--
Andreas Joseph Krogh <andreak@officenet.no>
Senior Software Developer / Manager
gpg public_key: http://dev.officenet.no/~andreak/public_key.asc
------------------------+---------------------------------------------+
OfficeNet AS            | The most difficult thing in the world is to |
Karenslyst Allé 11      | know how to do a thing and to watch         |
PO. Box 529 Skøyen      | somebody else doing it wrong, without       |
0214 Oslo               | comment.                                    |
NORWAY                  |                                             |
Mobile: +47 909  56 963 |                                             |
------------------------+---------------------------------------------+