简体   繁体   中英

SQL Server 2005 FOREIGN KEY that references a “constant” value for a second column

I have a table that has a foreign key to another table.
For example: Postion.day REFERENCES weekdays.day which is fine. However Position.day can hold weekdays where rdo=true .

The primary way of accessing this data is planned to be through a Web Application, that I'm witting. I plan on adding this check in the web-application anyway. I'm just looking for way to enforce as much data integrity at the DB level as I can short of writing triggers.

I suspect the answer to look something like:

ALTER TABLE Postition ADD COLUMN day CHAR(3)
            FOREIGN KEY REFERENCES weekday(shortName) 
            CHECK (weekday.rdo=TRUE);

Normally I would "try it and see", however, I have a lot of changes to make and I'm still finalizing my design and thought I would ask the experts and see what they had to say while I worked on the rest of it.

UPDATE:
ok So I have a table, Name Table (I didn't name it), I have another Table weekdays which lists All 7 days of the week along with some other info. Name Table has 2 Foreign Keys rdo and shortDay. weekdays holds a bit field for rdo and and a bit field for shortday stating if the day is eligible to used for those days. So I want my RDO field to be a foreign key to weekdays but ONLY WHERE RDO=TRUE.

Weekdays primary Key is shortname, 3 letters( char(3) ) representing a weekday, EG: Mon, Tue, Wed, Thu, etc

I was thinking about it and remembered SQL transactions.(I'm currently betting that SQL Server will be smart enough to rollback a successful ALTER TABLE call, the position table already exists.)

BEGIN
ALTER TABLE [Name Table] ADD RDO CHAR(3);
ALTER TABLE [Name Table] ADD FOREIGN KEY (RDO) REFERENCES weekdays(shortName);
ALTER TABLE [Name Table] ADD CHECK (TRUE=(SELECT rdo FROM weekdays 
                                                     WHERE shortName=RDO));
ROLLBACK;

Which returns from the Database:

Error code 102, SQL state S0001: Incorrect syntax near ')'.
Line 1, column 1

Error code 1769, SQL state S0001: Foreign key 'RDO' references invalid column 'RDO' in referencing table 'Name Table'.
Line 3, column 1

Error code 1046, SQL state S0001: Subqueries are not allowed in this context. Only scalar expressions are allowed.
Line 4, column 1

Error code 3903, SQL state S0001: The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION.
Line 5, column 1

So Adding the foreign Key is easy enough but I'm still stumped on how to reference the linked date in another table inside of a check constraint.

Ideally the Syntax would look like this(which I know is invalid):

ALTER TABLE [Name Table] ADD RDO CHAR(3)
            FOREIGN KEY REFERENCES weekdays(shortName,rdo=true);

does that help?

CHECK constraints are generally designed to work against the contents of a single row. That's why SELECT statements aren't allowed. If you're trying to determine whether the value stored in the column "day" is one of the weekdays, I'd do one of these things.

If the table "weekday" contains only the weekdays {'Mon', 'Tue', 'Wed', 'Thu', 'Fri'}, then all you need is a foreign key.

If the table "weekday" contains anything besides the weekdays {'Mon', 'Tue', 'Wed', 'Thu', 'Fri'}, I'd consider creating a different table of the five actual weekdays. Use whatever integrity constraints you think are necessary. The table "Position" can set a foreign key reference to that five-row table.

I might consider writing the entire constraint as a CHECK constraint.

ALTER TABLE Postition 
ADD COLUMN day CHAR(3)
CHECK (day IN ('Mon', 'Tue', 'Wed', 'Thu', 'Fri');

But I probably wouldn't do that. The advantage of having weekdays in a table is that you can use the table in an outer join to provide full weeks, even when some days might have no data. (But not every application needs that.)

Later . . .

To set a foreign key constraint to only those rows where RDO=TRUE , you need to identify those days as rows in a table. (Foreign keys target a column, not a column filtered by a value in another column.) Right now you have two Boolean flags. Consider creating two tables instead.

create table rdo (
  day char(3) primary key references weekdays (shortname)
);
insert into rdo values ('Mon');
insert into rdo values ('Tue');
insert into rdo values ('Wed');
insert into rdo values ('Thu');
insert into rdo values ('Fri');

create table shortday (
  day char(3) primary key references weekdays (shortname)
);
insert into shortday values ('Fri');

You can set foreign key references to either of those tables.

You'll get better answers if you provide DDL and sample data as SQL INSERT statements.

The way I'm currently working to achieve this is:

  • Add an extra bit column to Table , called eg IsRdo .
    • Add a check that Table.IsRdo = true . Yes, we have a column that's always 1 .
    • Or, I've just noticed, make it a constant: [IsRdo] AS cast(1 as bit) PERSISTED
  • Add a composite candidate key on weekdays consisting of (shortName, rdo) .
  • Make the foreign key from Table to weekdays match (shortName, rdo) .

This ensures that a Table row can only correspond to a weekdays row where rdo is true .

This seems a bit odd (again, we have a column that's always 1 ) but I've not found a better way to do it.

If it's really as simple as the days of the week then I would consider using a simple CHECK constraint on the hard-coded values for the weekdays. I'm more adamant than most against hard-coding things, but It's not like the days of the week are likely to change any time soon.

I thought that an FK on a filtered index (SQL 2008) or on a materialized view might solve the problem, but apparently neither of those approaches work :(

If you have control over the entire process (the code that calls the database and the database itself), then I would strongly recommend that you use stored procedures to perform your insert/update/deletes.

You would then implement your validations within the stored procedure prior to performing the actions and raiserror with the appropriate errors when you find an issue.

The primary benefit are

1) Your business rules are encapsulated and easy to understand when it it is time to change them in 6 months or a year

2) You can provide a much more meaningful error message to your users than that provide when a check constraint or other constraint is encountered. Users often get very confused when they see a strange exception.

In fact, because of item #2, we have removed all unique constraints from our tables and replaced them with equivalent stored procedure functionality that can tell the user exactly what is wrong and what they need to do to resolve the issue. Support calls have dropped significantly as a result.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM