views:

39

answers:

3

I have a table with a primary key (lets call it "person"), and another table that references it (lets call it "grade", as in student grades).

Table "grade" has field "grade.personid", which is a foreign key to "person.personid". Lets say "person" has field "person.type" as well (varchar with possible values of "student" or "teacher" for simplicity), and only students have grades.

How do I make the database reject any inserts/updates that put the "personid" of a non-student (i.e. teacher) in the "grade.personid" field.

I'm currently working with Sql Server 2008, but would be interested in answers for other platforms too.

                 [ grade  ]
[ person ]       [--------]
[--------]       [gradeid ]
[personid] <-FK- [personid]
[type    ]       [data    ]
[name    ]

p.s. I'm aware of constraints on schema bound views but don't really like them because they break whenever anyone modifies the tables they rely on.

+3  A: 

try this:

                 [ grade  ]
[ person ]       [--------]
[--------]       [gradeid ]
[personid] <-FK- [personid]
[type    ] <-FK- [type    ]
[name    ]       [data    ]

put a single FK on personid+type and add a check constraint on grade.type to only allow student type.

KM
I don't think it is a good idea. In my opinion, denormalization is useful for reports, but not for storing original data.
a1ex07
With the CHECK constraint, how would you have a teacher/role that shouldn't be allowed a supporting child record.
OMG Ponies
That indeed would work in my contrived example, and is quite imaginative, but I think it makes me cringe more than other possibilities (including do nothing or handle it in the application layer). Worth a +1 as an answer though, so thanks!
Tim Abell
OMG Ponies, would a teacher have a grade? the most realistic way for production code is the constraint on schema bound view as the OP mentions, or a trigger as @a1ex07 states. I was just trying to come up with a way to use a FK
KM
+1  A: 

In case of SQL Server desired logic can be implemented by defining INSTEAD OF trigger. For Mysql server, for instance, you will need to define BEFORE trigger.

UPDATE. Example of trigger(SQL server)

CREATE TRIGGER ON [grades] INSTEAD INSERT
AS
BEGIN
   IF NOT EXISTS(   
   SELECT 1 FROM [person] WHERE personid = inserted.person_id AND person.type  = 'student'
   )
   BEGIN
      RAISERROR ('Invalid person type', 10, 1);
   END;

   INSERT INTO [grades] SELECT field1, field2, ... FROM inserted;
END
GO

For mysql, it should be CREATE TRIGGER ... BEFORE INSERT. Also, mysql doesn't have an analogue of RAISEERROR, so you will need to generate an error to prevent inserting. Usually, I use INSERT INTO not_existing_table(id) VALUES(1,2) to get a runtime error in a trigger body.

a1ex07
Could you give a fuller example? I'm not sure how that would work. Thanks for the answer, I hadn't thought of triggers!
Tim Abell
I added an example. Hope it helps.
a1ex07
from within a trigger the best way to error out is to use RAISERROR (like you do) but also follow that up with a ROLLBACK then a RETURN command. see http://www.sommarskog.se/error-handling-I.html#triggercontext
KM
That's a fab answer. thanks a1ex07. I think this will be much more robust than the schema bound view option and sufficiently discoverable.
Tim Abell
@Tim Remember this only covers one side of the FK relationship. Also this approach can fail if you use snapshot isolation.
Martin Smith
Good point, so you could add a grade for a student, and then change them to become a teacher.
Tim Abell
I've never heard of snapshot isolation! (I'm not a sql server dba and don't particularly intend to become one).
Tim Abell
A: 

And for the record, here's an anonymized version of what worked for me:

CREATE TRIGGER trigRestrictGradeToStudents
ON dbo.grade
FOR INSERT, UPDATE
AS
IF NOT EXISTS (
    SELECT person.personid
    FROM person
        INNER JOIN inserted ON person.personid = inserted.personid
    WHERE (type = 'student')        
)
BEGIN
    ROLLBACK TRANSACTION --prevent the insert/update from proceeding
    RAISERROR ('Only students may have grade records', 11, 1)
END

Severity set to 11 to make it show in the error message before "The transaction ended in the trigger. The batch has been aborted."

See also RAISERROR (sic)

Tim Abell