1

Full Question:

From a child table linked to a row in its grandparent table by a foreign key (references grandparent primary key), is it possible to use a field from the referenced grandparent row in the child table's composite foreign key?

My current database design is has a main parent table, Projects. Projects has two child tables, JobTitles and Tasks. Tasks then has a child table Subtasks. Subtasks then has a child table Assignments, which is a x-ref table putting employees on Subtasks. The problem I've come up to is assigning JobTitles to Assignments; JobTitles belong to a Project, and as such the Assignments under any given Project should only be able to reference JobTitles that share a Project. I recently asked this question regarding limiting choice of JobTitle to those sharing a project. However, since then I've found a composite foreign key to be a much cleaner solution.

Simple database layout:

  • Projects
    • JobTitles
    • Tasks
      • Subtasks
        • Assignments

I've found how to create a composite key here, but for this I would need to use the Project's primary key as part of the composite foreign key.

Tables:

  • Projects
    • ProjectName (PK)
    • ProjectID (Unique Index)
  • JobTitles
    • JobTitle
    • ProjectID (Foreign Key -> Projects.ProjectID) (Composite PK: JobTitle-ProjectID)
    • JobTitleID (Unique Index)
  • Tasks
    • TaskName
    • ProjectID (Foreign Key -> Projects.ProjectID) (Composite PK: TaskName-ProjectID)
    • TaskID (Unique Index)
  • Subtasks
    • SubtaskName
    • TaskID(Foreign Key -> Tasks.TaskID) (Composite PK: SubtaskName-TaskID)
    • SubtaskID (Unique Index)
  • Assignments
    • EmployeeID (Foreign Key)
    • SubTaskID (Foreign Key -> Subtasks.SubtaskID) (Composite PK: EmployeeID-SubtaskID)
    • JobTitleID (Foreign Key -> JobTitles.JobTitleID)
    • AssignmentID (Unique Index)

In order to assign a JobTitle to an Assignment, I want to set up a composite foreign key using an Assignment's ProjectID (from its parent Task) and its selected JobTitleID. The only problem is that I have no idea how to grab the ProjectID, which is two generations away, for use in a key. It could be possible to pass the ProjectID down through each generation by wrapping it in a composite key within each table, but composite keys aren't something something to throw around willy nilly considering the toll they take on performance (not to mention passing the value down seems a bit sloppy). Is there any way to get to the ProjectID for use in the key without passing it through the other tables?

Community
  • 1
  • 1
PeaBucket
  • 142
  • 1
  • 1
  • 9

1 Answers1

2

As you say, you can pass the ProjectID down through each table. I don't think that's sloppy, and it enables you to create composite primary keys that enable reuse of e.g. TaskIDs across different projects. Once one realises that, one observes that the common practice of using ID columns in every table is a little superfluous. One can instead use semantically meaningful data as keys, which is usually my preference (sure it's more costly in terms of space, but the indexing results in relatively little impact on time):

CREATE TABLE Projects (
  ProjectName VARCHAR(20) NOT NULL PRIMARY KEY
);

CREATE TABLE JobTitles (
  ProjectName VARCHAR(20) NOT NULL,
  JobTitle    VARCHAR(20) NOT NULL,
  PRIMARY KEY (ProjectName, JobTitle),
  FOREIGN KEY (ProjectName) REFERENCES Projects (ProjectName)
);

CREATE TABLE Tasks (
  ProjectName VARCHAR(20) NOT NULL,
  TaskName    VARCHAR(20) NOT NULL,
  ParentTask  VARCHAR(20),
  PRIMARY KEY (ProjectName, TaskName),
  FOREIGN KEY (ProjectName) REFERENCES Projects (ProjectName),
  FOREIGN KEY (ProjectName, ParentTask) REFERENCES Tasks (ProjectName, TaskName)
);

CREATE TABLE Assignments (
  ProjectName VARCHAR(20)  NOT NULL,
  TaskName    VARCHAR(20)  NOT NULL,
  JobTitle    VARCHAR(20)  NOT NULL,
  Email       VARCHAR(255) NOT NULL,
  PRIMARY KEY (ProjectName, TaskName, JobTitle),
  FOREIGN KEY (ProjectName) REFERENCES Projects (ProjectName),
  FOREIGN KEY (ProjectName, TaskName) REFERENCES Tasks (ProjectName, TaskName),
  FOREIGN KEY (ProjectName, JobTitle) REFERENCES JobTitles (ProjectName, JobTitle),
  FOREIGN KEY (Email) REFERENCES Employees (Email)
);

Since MySQL doesn't support more powerful constraint validation, the only other option I can think of would be to define BEFORE INSERT and BEFORE UPDATE triggers on Assignments that raise errors to reject data that contains an invalid JobTitle. However, then you also would need to create triggers on JobTitles to handle situations where such referenced records change or are deleted; and then also on all the other tables that could give rise to a break in linkage. Ugly ugly ugly.

Hence my preference is with the first approach given above.

eggyal
  • 122,705
  • 18
  • 212
  • 237
  • Yeah, if I can't access the ProjectID directly then this is definitely the best way to do it. I really like the recursive tasks idea (I only recently learned to use the actual identifying index as the PK instead of the ID field), but the task layout is static (always project->task->subtask, no more/less). Thanks for your help! – PeaBucket Jun 29 '12 at 15:10