Há uma pergunta semelhante aqui usando um supertipo de mídia e adicionando subtipos de CD, videocassete, DVD, etc.
Isso é escalável, pois ao criar, digamos, um subtipo BluRay, você cria a tabela para conter os dados específicos do BluRay e adiciona uma entrada à tabela MediaTypes. Nenhuma alteração necessária para dados ou códigos existentes - exceto, é claro, para adicionar o código que funcionará com dados BluRay.
No seu caso, Users seria a tabela de supertipos com Teachers e Students as tabelas de subtipos.
create table Users(
ID int not null auto_generating,
Type char( 1 ) check( Type in( 'T', 'S' )),
-- other data common to all users,
constraint PK_Users primary key( ID ),
constraint UQ_UserType unique( ID, Type ),
constraint FK_UserTypes foreign key( Type )
references UserTypes( ID )
);
create table Teachers(
TeacherID int not null,
TeacherType char( 1 ) check( TeacherType = 'T' )),
-- other data common to all teachers...,
constraint PK_Teachers primary key( TeacherID ),
constraint FK_TeacherUser foreign key( TeacherID, TeacherType )
references Users( ID, Types )
);
A composição da tabela Alunos seria semelhante à tabela Professores.
Como professores e alunos podem empregar outros professores e alunos, a tabela que contém essa relação se referiria à tabela Usuários.
create table Employment(
EmployerID int not null,
EmployeeID int not null,
-- other data concerning the employment...,
constraint CK_EmploymentDupes check( EmployerID <> EmployeeID ),
constraint PK_Employment primary key( EmployerID, EmployeeID ),
constraint FK_EmploymentEmployer foreign key( EmployerID )
references Users( ID ),
constraint FK_EmploymentEmployee foreign key( EmployeeID )
references Users( ID )
);
Pelo que entendi, as Notificações são agrupadas por empregador:
create table Notifications(
EmployerID int not null
NotificationDate date,
NotificationData varchar( 500 ),
-- other notification data...,
constraint FK_NotificationsEmployer foreign key( EmployerID )
references Users( ID )
);
As consultas devem ser bastante simples. Por exemplo, se um usuário quiser ver todas as notificações de seu(s) empregador(es):
select e.EmployerID, n.NotificationDate, n.NotificationData
from Employment e
join Notifications n
on n.EmployerID = e.EmployerID
where e.EmployeeID = :UserID;
Este é um esboço inicial, é claro. Refinamentos são possíveis. Mas para seus pontos numerados:
- A tabela Emprego relaciona empregadores a empregados. A única verificação é fazer com que os empregadores de usuários não possam ser empregados, mas, caso contrário, qualquer usuário pode ser empregado e empregador.
- A tabela Users força cada usuário a ser um professor ('T') ou um aluno ('S'). Somente usuários definidos como 'T' podem ser colocados na tabela Professores e somente usuários definidos como 'S' podem ser colocados na tabela Alunos.
- A tabela Emprego se associa apenas à tabela Usuários, não às tabelas Professores e Alunos. Mas isso ocorre porque professores e alunos podem ser empregadores e funcionários, não por qualquer motivo de desempenho. Em geral, não se preocupe com o desempenho durante o projeto inicial. Sua principal preocupação neste momento é a integridade dos dados. Bancos de dados relacionais são muito bons com junções. Se um problema de desempenho deve surgir e corrigi-lo. Não reestruture seus dados para resolver problemas que ainda não existem e podem nunca existir.
- Bem, experimente e veja como funciona.