Parameterize validation rule and text?

L

Laurel

I have an application which is highly parameterized. That is, values in a
table drive a lot of what's going on, such as "maximum score." The one
thing that seems to require hands-on non "dumb user" manipulation is the
validation in the tables themselves. If I change the value of "maximum
score" from 5 to 3, for instance, then for each of the Score1, Score2, and
Score3 rows in the tblScores table, I have to change the validation rule
from "<=5" to "<=3", and the validation text from "The score must be <= 5 or
null." to "The score must be <=3 or null." Is there any way around this?

TIA
LAS
 
G

Guest

Hi Laurel

It's just an idea as I can't see your database so may not work. But if it
were me I was not refer to a specific number in the criteria I would refer to
the field/control holding the number. This way if you change the criteria
you will not not have to alter the formula.

Like this <=[FieldName]

Also as you may decide now and again just to have a look at different
combinations you could add an unbound control to the form and use the
AfterUpdate to check for Nul in this box and if you have added a number then
.........

Something like ...If Me.UnboundControl <> Null then <=[FieldName] etc etc etc

Hope this helps
 
J

John Nurick

Hi Laurel,

It's possible to do this sort of thing by adding check constraints to
the database. Most Access users don't use them, partly because you can
only manipulate them via SQL and not in table design. I believe you need
to switch the database to use "SQL Server compatible syntax" (in
Tools|Tables and Queries).

In my test database I set up tblConstraints with two fields,
C_Label* - text(20)
C_Value - number (long)
and added one record
Max_Score, 5

I have a field called Score in the table tblRon. Executing these two SQL
DDL statements (create a new query, switch to SQL view, type the
statement and run the query) added two constraints.

The first, "Exceeds_maximum_score", prevents you from entering a value
into tblRon.Score that is greater than the Max_score value stored in
tblConstraints. The second excludes scores below zero.

ALTER TABLE tblRON
ADD CONSTRAINT Exceeds_maximum_score CHECK (
Score <= (
SELECT C_Value FROM tblConstraints
WHERE C_Label = 'Max_score'
)
)
;

ALTER TABLE tblRON
ADD CONSTRAINT Score_cannot_be_less_than_zero CHECK (
Score >= 0
)
;


Having read this far you know about as much about check constraints as I
do. I suggest you try them out on a test database, and read up on them.
If you use Google Groups to search for something like this:

group:microsoft.public.access.tablesdbdesign collins constraint

you'll find posts by Jamie Collins who really understands this area.
Probably there's documentation at msdn.microsoft.com, but I haven't yet
got round to searching for it.

Good luck!
 
J

Jamie Collins

I have a field called Score in the table tblRon. Executing these two SQL
DDL statements (create a new query, switch to SQL view, type the
statement and run the query) added two constraints.

The first, "Exceeds_maximum_score", prevents you from entering a value
into tblRon.Score that is greater than the Max_score value stored in
tblConstraints.

ALTER TABLE tblRON
ADD CONSTRAINT Exceeds_maximum_score CHECK (
Score <= (
SELECT C_Value FROM tblConstraints
WHERE C_Label = 'Max_score'
)
)
;

FWIW I have experienced a few issues with this 'correlated' construct
to the point where I don't trust the engine to use the correct value
for 'Score' when an update affects more than one row. Also, such a
construct is slightly more challenging to write and debug. I prefer an
'assertion' type construct e.g. (aircode)

ALTER TABLE tblRON ADD
CONSTRAINT Exceeds_maximum_score
CHECK (
NOT EXISTS (
SELECT *
FROM tblConstraints AS C1, tblRON AS I1
WHERE I1.Score > C1.'Max_score'
)
)
;

To debug: drop the constraint, add some 'bad' data, run the query

SELECT *
FROM tblConstraints AS C1, tblRON AS I1
WHERE I1.Score > C1.'Max_score'

and see whether any of the bad data shows up.

Jamie.

--
 
J

John Nurick

FWIW I have experienced a few issues with this 'correlated' construct
to the point where I don't trust the engine to use the correct value
for 'Score' when an update affects more than one row. Also, such a
construct is slightly more challenging to write and debug. I prefer an
'assertion' type construct e.g. (aircode)

ALTER TABLE tblRON ADD
CONSTRAINT Exceeds_maximum_score
CHECK (
NOT EXISTS (
SELECT *
FROM tblConstraints AS C1, tblRON AS I1
WHERE I1.Score > C1.'Max_score'
)
)
;

To debug: drop the constraint, add some 'bad' data, run the query

SELECT *
FROM tblConstraints AS C1, tblRON AS I1
WHERE I1.Score > C1.'Max_score'

Thanks for chipping in, Jamie. This is an area I know little about but
it seemed an obvious solution to the OP's problem. But...

1) I'm not sure what you mean by C1.'Max_score' in the WHERE clause and
what's reason for the Cartesian join. ISTM that this does the job:

ALTER TABLE tblRON ADD
CONSTRAINT Exceeds_maximum_score
CHECK (
NOT EXISTS (
SELECT *
FROM tblRON
WHERE Score > (SELECT C_Value FROM tblConstraints
WHERE C_Label = 'Max_Score')
)
)
;

2) How would one modify this constraint to allow Null scores?












I think I see that the Cartesian join ensures that the value of Score is
always checked in all records
 
J

Jamie Collins

1) I'm not sure what you mean by C1.'Max_score' in the WHERE clause and
what's reason for the Cartesian join. ISTM that this does the job:

ALTER TABLE tblRON ADD
CONSTRAINT Exceeds_maximum_score
CHECK (
NOT EXISTS (
SELECT *
FROM tblRON
WHERE Score > (SELECT C_Value FROM tblConstraints
WHERE C_Label = 'Max_Score')
)
)

Sorry about that. My aircode was a 'cut and paste' based on your code
and I assumed Max_Score was an attribute rather than a data element.
Now that I see the giveaway quotes in your code, I wonder whether you
are committing the OTLT design flaw (google it).

I'd normally see a single row table and changing you design
accordingly, your above revision is semantically equivalent to mine so
it's the same difference. If yours makes more sense to you then that's
good because you'll find the code easier to maintain. Mine suits me
and is based on the presumption that the will optimizer prefer a
straightforward join to its subquery equivalent (but I wouldn't be
surprised if their respective 'execution plans' are identical).
Another presumption is the engine won't go far down the road of
returning a large resultset because it only takes one row to fail a
NOT EXISTS clause!
2) How would one modify this constraint to allow Null scores?

I think I see that the Cartesian join ensures that the value of Score is
always checked in all records

No, I actually think it's due to a bug. I think my CHECK constraint is
designed to handle the NULL value correctly and think that the engine
isn't. That's quite a bold statement so allow me to demonstrate with
some sample data.

Ignore for the moment that the usage for tblRON here is mightily
contrived:

CREATE TABLE tblConstraints (
lock CHAR(1) DEFAULT 'x' NOT NULL PRIMARY KEY,
CONSTRAINT tblConstraints__max_one_row
CHECK (lock = 'x'),
Max_score INTEGER NOT NULL
)
;
INSERT INTO tblConstraints (Max_score) VALUES (10)
;
CREATE TABLE tblRON (
player_name VARCHAR(15) NOT NULL UNIQUE,
Score INTEGER
)
;

I'm not creating the constraint at this time and instead adding some
test data:

INSERT INTO tblRON (player_name, Score) VALUES ('Rick', NULL)
;

Now try the query intended for the constraint:

SELECT *
FROM tblConstraints AS C1, tblRON AS I1
WHERE I1.Score > C1.Max_score

It returns an empty set, therefore a row with the NULL value for Score
should not fail the constraint. Now let's test it for real:

DELETE FROM tblRON
;
ALTER TABLE tblRON ADD
CONSTRAINT Exceeds_maximum_score
CHECK (
NOT EXISTS (
SELECT *
FROM tblConstraints AS C1, tblRON AS I1
WHERE I1.Score > C1.Max_score
)
)
;
INSERT INTO tblRON (player_name, Score) VALUES ('Rick', NULL)
;

Oh dear, the CHECK constraint bites when it should not! A blatant bug
and one with little hope of being fixed, I'm afraid. Note your
construct also erroneously chokes on the NULL value, it's nothing to
do with the join type.

Adding a 'hint' to the WHERE clause seems to do the trick:

SELECT *
FROM tblConstraints AS C1, tblRON AS I1
WHERE I1.Score > C1.Max_score
AND I1.Score IS NOT NULL

but that strikes me as a bit daft and I'm more inclined to conclude
the engine is not fit for purpose, considering the rate at which such
issues are addressed :(

As regards handling the NULL value, to be honest I have so few columns
that accept the NULL value that I hardly ever need to bother with them
in constraints (a win:win situation <g>!) I pointed out the above
example was contrived because it strikes me that if the player doesn't
yet have a score the they should be entered into the table and if the
was no other way of recording a player then I'd be concerned about
delete anomalies (redundancy) in the model. For inspiration on how to
avoid such situations, see:

How To handle Missing Information Without Using NULL
http://www.dcs.warwick.ac.uk/~hugh/TTM/Missing-info-without-nulls.pdf

FWIW I don't think it's a Cartesian join (FWIW more lately know as
CROSS JOIN as per the SQL-92 standard). Because it has a join
condition, I'd consider it an INNER JOIN e.g. this is semantically
equivalent again:

SELECT *
FROM tblConstraints AS C1
INNER JOIN tblRON AS I1
ON I1.Score > C1.Max_score

For an INNER JOIN, a search condition (WHERE) and a join condition
(ON) should be interchangeable but Access/Jet is fussy about certain
join conditions e.g. it wouldn't like this:

SELECT *
FROM tblConstraints AS C1
INNER JOIN tblRON AS I1
ON I1.Score NOT BETWEEN C1.Min_score AND C1.Max_score

but would be fine with this:

SELECT *
FROM tblConstraints AS C1, tblRON AS I1
WHERE I1.Score NOT BETWEEN C1.Min_score AND C1.Max_score

So I guess I've got into the habit of favouring a search condition
too :)
Probably there's documentation at msdn.microsoft.com, but I haven't yet
got round to searching for it.

I've looked and I really think there is nothing to see. I find it
quite surprising that this whole area of functionality is
undocumented. Remember, CHECK constraints are mentioned as a feature
of Access/Jet therefore they are supported (whatever that means). My
theory is, they think that if they don't tell us what it's supposed to
do then we can't find bugs with it. My stance is, if they don't tell
us what it's supposed to do then they should be compliant with the
SQL-92 standard, which is very detailed about how table constraints
should work (and they are not compliant as regards timing of checks
but that's a whole other post).

With all that in mind, here's a suggestion on how to avoid the table-
level CHECK constraint by instead using declarative referential
integrity. Something like this:

CREATE TABLE tblConstraints (
lock CHAR(1) DEFAULT 'x' NOT NULL PRIMARY KEY,
CONSTRAINT tblConstraints__max_one_row CHECK (lock = 'x'),
Max_score INTEGER NOT NULL UNIQUE
)
;
CREATE TABLE tblRON (
player_name VARCHAR(15) NOT NULL UNIQUE,
Score INTEGER,
Max_score INTEGER NOT NULL
REFERENCES tblConstraints (Max_score)
ON UPDATE CASCADE,
CONSTRAINT Exceeds_maximum_score CHECK (Score <= Max_score)
)
;

INSERT INTO tblConstraints (Max_score) VALUES (10)
;
INSERT INTO tblRON (player_name, Score, Max_score)
SELECT 'Jamie', 3, Max_score
FROM tblConstraints
;
INSERT INTO tblRON (player_name, Score, Max_score)
SELECT 'Rick', NULL, Max_score
FROM tblConstraints
;
INSERT INTO tblRON (player_name, Score, Max_score)
SELECT 'John', 99, Max_score
FROM tblConstraints
;

Note only the final INSERT makes the constraint bite and that the NULL
is handled properly (i.e. allowed in the above scenario). The CHECK in
this case could be replaced by a record-level Validation Rule.

Jamie.

--
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top