Implementing Table Inheritance in SQL Server

本文介绍了一种在 SQL Server 中实现表继承的方法,通过创建基础表和子表来简化多类型实体的数据建模,同时确保了数据一致性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

Implementing Table Inheritance in SQL Server

By Jeff Smith on 20 February 2008 http://www.sqlteam.com/ 

 


 

 

When designing a database, we sometimes come across situations where there are multiple types of entities that we are modeling, but we'd like them to all have certain attributes or relations in common. Using "sub-type" tables is a simple way to implement table inheritance in SQL Server.

 

For example, a question came up recently about modeling the following entities in a "School" database:

  • Students
  • Teachers
  • Parents

Each of those entities has many of the same attributes, such as first name, last name, middle name, and birth date. Yet, we must separate them into multiple tables because we need to store and track different data for students, teachers and parents: students have grades and classes and parents; teachers have classes taught, skills, employment information, and so on.

In addition to sharing common attributes, these entities also have common relations. For example, for each of those entities we might also like to store addresses, phone numbers, correspondence history, etc. To do this in a nicely normalized database, we would model that data by creating additional tables:

  • StudentsAddresses
  • TeachersAddresses
  • ParentsAddresses
  • StudentPhoneNumbers
  • TeacherPhoneNumbers
  • ParentPhoneNumbers
  • StudentCorrespondance
  • TeacherCorrespondance
  • ParentCorrespondance
  • ...etc...

On top of the redundant, similar tables, we would need a whole mess of redundant, similar stored procedures to add/update/delete/select items from these tables. Yet, again, we need different tables for these different entities because they each have their own set of relations and attributes to track.

Is there an easier way to model this in a relational database? Absolutely -- let's take a look.

Creating A "Base Table"

We can start by recognizing that Students, Teachers, and Parents are all "People", and we can note that it makes sense to say that all People can have addresses and phone numbers and correspondence history:

  • People
  • PeopleAddresses
  • PeoplePhoneNumbers
  • PeopleCorrespondence

In the People table, we'd store all of the common attributes of Students, Teachers and Parents that we discussed earlier: name, birth date, and so on. We remove all of these common columns from the Students, Teachers and Parents tables and put them all in one place. Now, maintaining phone numbers, addresses, names, birthdays, and correspondence can all be done with one set of generic stored procedures. The redundancy of those activities has now been reduced, and any changes in phone number or address formats can all be done in place. We can refer to the People table as a "base table".

Deriving Sub-Tables from the Base Table

Of course, we still need our Students, Teachers and Parents tables -- but now the primary key of these tables also becomes a foreign key to the People table.

Because any row in the Students, Teachers or Parents tables require a related row in the People table, and it also shares the same primary key as the People table (i.e., it is a 1:1 relation), I'll refer to the Students, Teachers and Parents tables as sub-tables of the People table. Think of these sub-tables as tables that extend the basic information that the People table provides; this is similar to how inheritance works in Object-Oriented Programming (OOP). Essentially, we are performing "table inheritance" by doing this; since every student, teacher and parent is by definition also a "person" and we are guaranteed that we can work with all of those entities the same way by treating them as People if we want, or we can work with them using their specific attributes and relations.

That gives us a schema like this:

create table People(PersonID int primary key, Name varchar ... etc ...)

create table Students(PersonID int primary key references People(PersonID), ...)

create table Teachers(PersonID int primary key references People(PersonID), ...)

create table Parents(PersonID int primary key references People(PersonID), ...)

With that particular schema, notice a few things:

  1. You cannot create a Student/Teacher/Parent until you first set up the "Person". Once the person is created, you can now add a related row to the appropriate table, depending what type they are.
  2. We still have our common data all in one place, but now we have tables set up to store specific attributes and relations for these different types of People. A simple JOIN from Students to People gives us the student's name, contact information, and so on.
  3. This schema allows a person to be a student, teacher and parent -- all at once! It also allows a person to be none of these -- you simply don't add a related row in any of the sub-tables.
  4. If we want to output a "Type" column (Student, Teacher, Parent) when querying the People table, we must outer join to all the 3 "sub-tables" and determine if any matching rows exist.

That 3rd point is important, as it may not be what you want; we'll get back to that in a moment. But for our purposes, I think it makes sense: suppose a person is initially a student, comes back to work for the school as a teacher, and then eventually has kids of their own. This schema allows us to handle that.

The 4th point is important to consider as well. We could add a "PersonType" column to the table, and create a table of PersonTypes -- but then we introduce redundancies and the possibility of conflicting data. Also, as mentioned, a person can be more than one "type" at a time, so the existence of this column doesn't really make sense in this table without changing our logical model.

Using this schema is easy; we can quickly get all of the information for all of the students with a simple join:

select s.*, p.*
from students s 
inner join people p on s.personID = p.personID

(you'd replace * with actual columns that you need, of course.)

So, everything works beautifully. We have our base table, we have our sub-tables, and the inheritance allows us to work with the different entities all the same way, but to also treat each of them specifically on their own as necessary.

Modeling One-to-Either Constraints

However, what if we want to set up our model so that a Person can have only one type; i.e., either a Student, OR a teacher, OR a parent? This is a more accurate database implementation of inheritance, since in OOP you cannot create an instance of something that is more than one sub-class at a time -- you must pick one or the other (or potentially the base class itself, if that's what you need).

Modeling this can be tricky, and it can be done a couple of ways. Here’s a technique I like to use, it doesn’t require a trigger, it is easy to implement, and it uses standard referential integrity without complicated check constraints.

First, create a table of PersonTypes:

create table PersonType (PersonTypeID int primary key, PersonType varchar(10))

And let’s insert the following values, one per “sub-table”:

insert into PersonType
select 1,'Student' union all
select 2,'Teacher' union all
select 3,'Parent'

This is the table that defines the different types our system will allow. There will be one entry in this table per "sub-table" that we create. If we want to allow for a Person to be just a generic "Person" without any specify sub-type, we could add a row for "Other".

With that table in place, we will now add a PersonType column to our People table. For this example, let’s only define a generic Name column for our People table:

create table People
(
  PersonID int primary key, 
  PersonTypeID int references PersonType(PersonTypeID) not null, 
  Name varchar(10) not null
)

Pretty standard stuff so far – we have different types of People we are modeling, and each Person must be assigned a type.

And now comes the trick: We will add an extra PersonType column to each of our sub-tables, and force the value in that column to be equal to the PersonTypeID for that table. In SQL 2000, we can just add a default and a check constraint, but in SQL 2005 we can use a persisted computed column. So, our Students table will have a constant PersonType value of 1, the Teachers table a value of 2, and the Parents table a value of 3.

Then, we simply create a foreign key relation back to the People table – but instead of just on the PersonID column, we create the constraint on both the PersonID and the PersonTypeID columns. With these constraints in place, we are guaranteed that Person will only have one type, and that they can only have data in the matching sub-table for that type.

There is a catch, however: we cannot create our sub-tables just yet; if we try it, we’ll get the following error:

create table Students 
(
  PersonID int primary key, 
  PersonTypeID as 1 persisted, -- student
  EnrollmentDate datetime, 
  foreign key (PersonID, PersonTypeID) references People(PersonID, PersonTypeID)
)

Msg 1776, Level 16, State 0, Line 1
There are no primary or candidate keys in the referenced table 'People' that 
match the referencing column list in the foreign key 'FK__Students__1F98B2C1'.
Msg 1750, Level 16, State 0, Line 1
Could not create constraint. See previous errors.

This is because the unique constraint on the People table is just on the PersonID column, but we are trying to set up a foreign key constraint on the combination of PersonID/PersonTypeID. To handle this, we simply add an additional unique constraint to the People table, covering both PersonID and PersonTypeID:

drop table People
go

create table People
(
  PersonID int primary key, 
  PersonTypeID int references PersonType(PersonTypeID), 
  Name varchar(10)
  constraint People_AltPK unique (PersonID,PersonTypeID)
)

With that in place, we are good to go:

create table Students 
(
  PersonID int primary key, 
  PersonTypeID as 1 persisted, -- student
  EnrollmentDate datetime, 
  foreign key (PersonID, PersonTypeID) references People(PersonID, PersonTypeID)
)

create table Teachers 
(
  PersonID int primary key, 
  PersonTypeID as 2 persisted, -- teacher
  HireDate datetime, 
  foreign key (PersonID, PersonTypeID) references People(PersonID, PersonTypeID)
)

create table Parents
(
  PersonID int primary key, 
  PersonTypeID as 3 persisted, -- parents
  DifficultyScore int,
  foreign key (PersonID, PersonTypeID) references People(PersonID, PersonTypeID)
)

Command(s) completed successfully.

Note: If you are using SQL Server 2000, instead of computed columns for the PersonTypeID constants, you would instead use a default value and a check constraint, since it does not allow computed columns to be used in foreign key constraints:

create table Teachers -- SQL 2000 Version 
(
  PersonID int primary key, 
  PersonTypeID int not null default 2 check (PersonTypeID = 2), -- teachers type
  HireDate datetime, 
  foreign key (PersonID, PersonTypeID) references People(PersonID, PersonTypeID)
)

Now to test it. Let’s create a new Person, called "Bob", and make him a student. First, we add a row to the People table, and then a matching row to the Student table:

insert into People (PersonID, PersonTypeID, Name)
values (1,1,'Bob')

(1 row(s) affected)

insert into Students (PersonID, EnrollmentDate)
values (1,'2007-01-01')

(1 row(s) affected)

Notice that we cannot add teacher information for Bob, since he is a student:

insert into Teachers (PersonID, HireDate)
values (1,'2007-01-01')

Msg 547, Level 16, State 0, Line 1
The INSERT statement conflicted with the FOREIGN KEY constraint "FK__Teachers__31B762FC". 
The conflict occurred in database "PlayGround", table "dbo.People".
The statement has been terminated.

And, of course, we cannot change Bob’s type from a student to a teacher, since there is already a row in the Students table for him:

update People set PersonTypeID=2 where PersonID = 1

Msg 547, Level 16, State 0, Line 1
The UPDATE statement conflicted with the REFERENCE constraint "FK__Students__2645B050". 
The conflict occurred in database "PlayGround", table "dbo.Students".
The statement has been terminated.

The only way to change Bob’s type is to remove the row from the Students table first:

-- delete student attributes:
delete from Students where PersonID=1

-- change the type from a student to to a teacher
update People set PersonTypeID=2 where PersonID = 1

-- add teacher attributes:
insert into Teachers (PersonID, HireDate)
values (1,'2007-01-01')

(1 row(s) affected)

(1 row(s) affected)

(1 row(s) affected)

And there you have it! A "one-to-either" constraint, where each row in one table has a related row in only one of several possible tables, without the need for any triggers or any complicated T-SQL programming to maintain data integrity.

Conclusion

Once things are in place, we have a simpler data model with fewer tables and stored procedures, and we are guaranteed consistency when dealing with common attributes and relations of "People" in the database. We can easily create new types of People simply by adding a row to the PersonTypes table and then creating a new "sub-table". As soon as the new sub-table is created, that new type immediately has all of the attributes and relations of a Person – without coding up anything or making any other changes to the database!

标题基于SpringBoot+Vue的学生交流互助平台研究AI更换标题第1章引言介绍学生交流互助平台的研究背景、意义、现状、方法与创新点。1.1研究背景与意义分析学生交流互助平台在当前教育环境下的需求及其重要性。1.2国内外研究现状综述国内外在学生交流互助平台方面的研究进展与实践应用。1.3研究方法与创新点概述本研究采用的方法论、技术路线及预期的创新成果。第2章相关理论阐述SpringBoot与Vue框架的理论基础及在学生交流互助平台中的应用。2.1SpringBoot框架概述介绍SpringBoot框架的核心思想、特点及优势。2.2Vue框架概述阐述Vue框架的基本原理、组件化开发思想及与前端的交互机制。2.3SpringBoot与Vue的整合应用探讨SpringBoot与Vue在学生交流互助平台中的整合方式及优势。第3章平台需求分析深入分析学生交流互助平台的功能需求、非功能需求及用户体验要求。3.1功能需求分析详细阐述平台的各项功能需求,如用户管理、信息交流、互助学习等。3.2非功能需求分析对平台的性能、安全性、可扩展性等非功能需求进行分析。3.3用户体验要求从用户角度出发,提出平台在易用性、美观性等方面的要求。第4章平台设计与实现具体描述学生交流互助平台的架构设计、功能实现及前后端交互细节。4.1平台架构设计给出平台的整体架构设计,包括前后端分离、微服务架构等思想的应用。4.2功能模块实现详细阐述各个功能模块的实现过程,如用户登录注册、信息发布与查看、在线交流等。4.3前后端交互细节介绍前后端数据交互的方式、接口设计及数据传输过程中的安全问题。第5章平台测试与优化对平台进行全面的测试,发现并解决潜在问题,同时进行优化以提高性能。5.1测试环境与方案介绍测试环境的搭建及所采用的测试方案,包括单元测试、集成测试等。5.2测试结果分析对测试结果进行详细分析,找出问题的根源并
内容概要:本文详细介绍了一个基于灰狼优化算法(GWO)优化的卷积双向长短期记忆神经网络(CNN-BiLSTM)融合注意力机制的多变量多步时间序列预测项目。该项目旨在解决传统时序预测方法难以捕捉非线性、复杂时序依赖关系的问题,通过融合CNN的空间特征提取、BiLSTM的时序建模能力及注意力机制的动态权重调节能力,实现对多变量多步时间序列的精准预测。项目不仅涵盖了数据预处理、模型构建与训练、性能评估,还包括了GUI界面的设计与实现。此外,文章还讨论了模型的部署、应用领域及其未来改进方向。 适合人群:具备一定编程基础,特别是对深度学习、时间序列预测及优化算法有一定了解的研发人员和数据科学家。 使用场景及目标:①用于智能电网负荷预测、金融市场多资产价格预测、环境气象多参数预报、智能制造设备状态监测与预测维护、交通流量预测与智慧交通管理、医疗健康多指标预测等领域;②提升多变量多步时间序列预测精度,优化资源调度和风险管控;③实现自动化超参数优化,降低人工调参成本,提高模型训练效率;④增强模型对复杂时序数据特征的学习能力,促进智能决策支持应用。 阅读建议:此资源不仅提供了详细的代码实现和模型架构解析,还深入探讨了模型优化和实际应用中的挑战与解决方案。因此,在学习过程中,建议结合理论与实践,逐步理解各个模块的功能和实现细节,并尝试在自己的项目中应用这些技术和方法。同时,注意数据预处理的重要性,合理设置模型参数与网络结构,控制多步预测误差传播,防范过拟合,规划计算资源与训练时间,关注模型的可解释性和透明度,以及持续更新与迭代模型,以适应数据分布的变化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值