1

I'm preparing a legacy Microsoft SQL Server database so that I can interface with in through an ORM such as Entity Framework, and my question revolves around handling the setup of some of my many-to-many associations that share a common type. Specifically, should a common type be shared among master types or should each master type have its own linked table?

For example, here is a simple example I concocted that shows how the tables of interest are currently setup:

legacy many-to-many

Notice that of there are two types, Teachers and Students, and both can contain zero, one, or many PhoneNumbers. The two tables, Teachers and Students, actually share an association table (PeoplePhoneNumbers). The field FKID is either a TeacherId or a StudentId.

The way I think it ought to be setup is like this:

better many-to-many?

This way, both the Teachers table and the Students table get its own PhoneNumbers table.

My gut tells me the second way is the proper way. Is this true? What about even if the PhoneNumbers tables contains several fields? My object oriented programmer brain is telling me that it would be wrong to have several identical tables, each containing a dozen or so fields if the only difference between these tables is which master table they are linked to? For example:

redundant tables

Here we have two tables that contain the same information, yet the only difference is that one table is addresses for Teachers and the other is for Students. These feels redundant to me and that they should really be one table -- but then I lose the ability for the database to constrain them (right?) and also make it messier for myself when I try to apply an ORM to this.

Should this type of common type be merged or should it stay separated for each master type?

Update

The answers below have directed me to the following solution, which is based on subclassing tables in the database. One of my original problems was that I had a common table shared among multiple other tables because that entity type was common to both the other tables. The proper way to handle that is to subclass the shared tables and essentially descend them from a common parent AND link the common data type to this new parent. Here's an example (keep in mind my actual database has nothing to do with Teachers and Students, so this example is highly manufactured but the concepts are valid):

solution

Since Teachers and Students both required PhoneNumbers, the solution is to create a superclass, Party, and FK PhoneNumbers to the Party table. Also note that you can still FK tables that only have to do with Teachers or only have to do with Students. In this example I also subclassed Students and PartTimeStudents one more level down and descended them from Learners.

Where this solution is very satisfactory is when I implement it in an ORM, such as Entity Framework.

The queries are easy. I can query all Teachers AND Students with a particular phone number:

var partiesWithPhoneNumber = from p in dbContext.Parties
     where p.PhoneNumbers.Where(x => x.PhoneNumber1.Contains(phoneNumber)).Any()
     select p;

And it's just as easy to do a similar query but only for PhoneNumbers belonging to only Teachers:

var teachersWithPhoneNumber = from t in dbContext.Teachers
  where t.Party.PhoneNumbers.Where(x => x.PhoneNumber1.Contains(phoneNumber)).Any()
  select t;
Brad Rem
  • 6,036
  • 2
  • 25
  • 50

4 Answers4

2

I think you should look into the supertype/subtype pattern. Add a Party or Person table that has one row for every teacher or student. Then, use the PartyID in the Teacher and Student tables as both the PK and FK back to Party (but name them TeacherID and StudentID). This establishes a "one-to-zero-or-one" relationship between the supertype table and each of the subtype tables.

Note that if you have identity columns in the subtype tables they will need to be removed. When creating those entities going forward you will first have to insert to the supertype and then use that row's ID in either subtype.

To maintain consistency you will also have to renumber one of your subtype tables so its IDs do not conflict with the other's. You can use SET IDENTITY_INSERT ON to create the missing supertype rows after that.

The beauty of all this is that when you have a table that must allow only one type such as Student you can FK to that table, but when you need an FK that can be either--as with your Address table--you FK to the Party table instead.

A final point is to move all the common columns into the supertype table and put only columns in the subtypes that must be different between them.

Your single Phone table now is easily linked to PartyID as well.

For a much more detailed explanation, please see this answer to a similar question.

Community
  • 1
  • 1
ErikE
  • 48,881
  • 23
  • 151
  • 196
  • Thanks @ErikE! You and Godeke have provided me with essentially the same answer to my question. I wish I could accept both, but since I can't, I've decided to +1 and accept Godeke and +1 your answer and also +1 your other, linked answer, as both your answers were helpful in providing examples. – Brad Rem Jan 14 '13 at 23:50
  • Thanks @Brad! For what it's worth, when two answers are basically the same, it's standard protocol to go with the one first posted. It's easy to see the posting order by clicking the "Oldest" tab to the bottom right of your question. – ErikE Jan 15 '13 at 00:08
2

Teacher and Student are both subclasses of a more general concept (a Person). If you create a Person table that contains the general data that is shared for all people in your database and then create Student and Teacher tables that link to Person and contain any additional details you will find that you have an appropriate point to link in any other tables.

If there is data that is common for all people (such as zero to many phone numbers) then you can link to the Person table. When you have data that is only appropriate for a Student you link it to the Student ID. You gain the additional advantage that Student Instructors are simply a Person with both a Student and Teacher record.

Some ORMs support the concept of subclass tables directly. LLBLGen does so in the way I describe so you can make your data access code work with higher level concepts (Teacher and Student) and the Person table will be managed on your behalf in the low level data access code.

Edit

Some commentary on the current diagram (which may not be relevant in the source domain this was translated from, so a pinch of salt is advised).

Party, Teachers and Learners looks good. Salaries looks good if you add start and end dates for the rate so you can track salary history. Also keep in mind it may make sense to use PartyID (instead of TeacherID) if you end up with multiple entites that have a Salary.

PartyPhoneNumbers looks like you might be able to hang the phone number off of that directly. This would depend on if you expect to change the phone number for multiple people (n:m) at once or if a phone number is owned by each Party independently. (I would expect the latter because you might have a student who is a (real world) child of a teacher and thus they share a phone number. I wouldn't want an update to the student's phone number to impact the teacher, so the join table seems odd here.)

Learners to PaymentHistories seems right, but the Students vs PartTimeStudents difference seems artificial. (It seems like PartTimeStudents is more AttendenceDays which in turn would be a result of a LearnerClasses join).

Godeke
  • 16,131
  • 4
  • 62
  • 86
  • this has succinctly explained it to me. What I don't like about my current database is that it has foreign keys that reference multiple tables. Now, I understand how to arrange my tables so that common data stays with common data. I've done some testing today, brought it into Entity Framework, and wrote some good unit tests. I feel very confident about this turning out well. – Brad Rem Jan 14 '13 at 23:46
  • A foreign key that references multiple tables isn't really a foreign key. It can't be enforced by the database (as there is no singular primary table) and it requires all kinds of wonky views to be created over time to pretend the multiple tables aren't there for the client side code. I'm glad to hear that your preliminary testing is going well. – Godeke Jan 15 '13 at 00:04
1

The problem that you have is an example of a "one-of" relationship. A person is a teacher or a student (or possibly both).

I think the existing structure captures this information best.

The person has a phone number. Then, some people are teachers and some are students. The additional information about each entity is stored in either the teacher or student table. Common information, such as name, is in the phone table.

Splitting the phone numbers into two separate tables is rather confusing. After all, a phone number does not know whether it is for a student or a teacher. In addition, you don't have space for other phone numbers, such as for administrative staff. You also have a challenge for students who may sometimes teach or help teach a class.

Gordon Linoff
  • 1,242,037
  • 58
  • 646
  • 786
0

Reading your question, it looks like you are asking for a common database schema to your situation. I've seen several in the past, some easier to work with than others.

One option is having a Student_Address table and a Teacher_Address table that both use the same Address table. This way if you have entity specific fields to store, you have that capability. But this can be slightly (although not significantly) harder to query against.

Another option is how you suggested above -- I would probably just add a primary key on the table. However you'd want to add a PersonTypeId field to that table (PersonTypeId which links to a PersonType table). This way you'd know which entity was with each record.

I would not suggest having two PhoneNumber tables. I think you'll find it much easier to maintain with all in the same table. I prefer keeping same entities together, meaning Students are a single entity, Teachers are a single entity, and PhoneNumbers are the same thing.

Good luck.

sgeddes
  • 62,311
  • 6
  • 61
  • 83