I have a table whose primary key is also a foreign key to the primary key of another table (i.e. "inheritance" as simulated in a database).
/****** Object: Table [dbo].[BaseClass] Script Date: 07/15/2011 18:17:27 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[BaseClass](
[ID] [int] IDENTITY(1,1) NOT NULL,
[Title] [nvarchar](50) NOT NULL,
[Description] [nvarchar](max) NOT NULL,
CONSTRAINT [PK_BaseClass] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
USE [TestConcepts]
GO
/****** Object: Table [dbo].[DerivedTable] Script Date: 07/15/2011 18:17:49 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[DerivedTable](
[ID] [int] IDENTITY(1,1) NOT NULL,
[SpecialProperty] [uniqueidentifier] NOT NULL,
CONSTRAINT [PK_DerivedTable] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[DerivedTable] WITH CHECK ADD CONSTRAINT [FK_DerivedTable_BaseClass] FOREIGN KEY([ID])
REFERENCES [dbo].[BaseClass] ([ID])
GO
ALTER TABLE [dbo].[DerivedTable] CHECK CONSTRAINT [FK_DerivedTable_BaseClass]
GO
What is the proper way to insert records in this situation? Obviously an insert doesn't return the PK of the inserted row (plus the child's table's PK is identity as well).
Here are a few examples of this pattern.
The sub-type table should not have auto-increment ID, the ID matches the one in the super-type table.
The basic technique (using your example) looks something like
DECLARE #MY_ID integer;
INSERT INTO BaseTable(Title, Description)
VALUES ('title_here', 'blah, blah');
SELECT #MY_ID = SCOPE_IDENTITY();
INSERT INTO DerivedTable(ID, SpecialProperty)
VALUES (#MY_ID, newid()); -- the SpecialProperty is uniqueidentifier
One approach is to create a view, one for each sub-type table, or just one over all of them. Then create an INSTEAD OF INSERT TRIGGER on the view and use the technique inside the trigger.
You may also find this technique for capturing multiple inserted IDs useful too.
Nobody calls this "inheritance". That's not what it is. It's a relation, the R in RDBMS.
INSERTs do tell you the PK of the just-inserted row. Look at ##SCOPE_IDENTITY on SQL Server.
The DERIVEDCLASS table could have an auto-incremented (identity) PK but if so there must be another column that is a foreign key reference back to BASETABLE:
BASETABLE
id int pk autoincrement
baseattribute1
baseattribute2
etc etc
DERIVEDTABLE
id int pk autoincrement
**baseid** foreign key references BASETABLE(id)
extendedattribute1
extendedattribute2
This would permit multiple derivations of each base entity. Placing a unique index on DERIVEDTABLE.baseid or making baseid the PK would prevent this, if that is desired.
The following would instantiate the members of the base class and their derived instances and/or extended properties if any [which it is will depend on whether baseid has unique constraint in DerivedTable; if the latter it could be the PK in a one-to-one relationship with BaseTable, rather than a many-to-one]:
select * from BASETABLE
LEFT JOIN DERIVEDTABLE
on BASETABLE.id = DERIVEDTABLE.baseid
Instances of the base class that have not been extended will have NULL in the extendedattribute columns.
To find only those entities that have been extended use an inner join:
select * from DERIVEDTABLE
inner join BASETABLE
on DERIVEDTABLE.baseid = BASETABLE.id
Related
I want to delete a User (parent table) which has blog comment(s) and blog comment reply(s)
I have it coded to 1st delete the BlogCommentReply (child to the BlogComment), then the BlogComment (parent to BlogCommentReply), then the user (parent to both).
I get the error:
The DELETE statement conflicted with the REFERENCE constraint "FK_BlogCommentReply_UserId". The conflict occurred in database "DBGbngDev", table "dbo.BlogCommentReply", column 'UserId'.
I have FK keys on the BlogCommentReply and BlogComment tables.
1.) Did I create the table structure correctly?
2.) Do I need cascades - why?
3.) Is the delete code order correct?
_ I am of the belief that the parent table cannot be deleted until the
children are deleted first. Is that correct?
Table creates:
CREATE TABLE [dbo].[User]
(
[UserId] [int] IDENTITY(1,1) NOT NULL, -- PK
other columns....
CONSTRAINT [PK_User] PRIMARY KEY CLUSTERED
(
[UserId] ASC
) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS =
ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[BlogComment]
(
[BlogCommentId] [int] IDENTITY(1,1) NOT NULL, -- PK
[UserId] [int] NOT NULL, -- FK
other columns....
CONSTRAINT [PK_BlogComment] PRIMARY KEY CLUSTERED
(
[BlogCommentId] ASC
) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[BlogComment] WITH CHECK ADD CONSTRAINT [FK_BlogComment_UserId] FOREIGN
KEY([UserId]) REFERENCES [dbo].[User] ([UserId])
GO
CREATE TABLE [dbo].[BlogCommentReply]
(
[BlogCommentReplyId] [int] IDENTITY(1,1) NOT NULL, -- PK
[UserId] [int] NOT NULL, -- FK
[BlogCommentId] [int] NOT NULL, -- FK
other columns....
CONSTRAINT [PK_BlogCommentReply] PRIMARY KEY CLUSTERED
(
[BlogCommentReplyId] ASC
) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS =
ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[BlogCommentReply] WITH CHECK ADD CONSTRAINT [FK_BlogCommentReply_UserId] FOREIGN
KEY([UserId]) REFERENCES [dbo].[User] ([UserId])
GO
ALTER TABLE [dbo].[BlogCommentReply] WITH CHECK ADD CONSTRAINT [FK_BlogCommentReply_BlogCommentId]
FOREIGN KEY([BlogCommentId]) REFERENCES [dbo].[BlogComment] ([BlogCommentId])
GO
Stored procedure (simplified for discussion) that does the deletes:
DELETE dbo.BlogCommentReply
FROM dbo.BlogComment a
WHERE ( BlogCommentReply.BlogCommentId = a.BlogCommentId AND BlogCommentReply.UserId = #a_UserId )
DELETE dbo.BlogComment
WHERE UserId = #a_UserId
DELETE dbo.[User]
WHERE ( UserId = #a_UserId )
It appears that your FK's are doing their jobs and preventing you from creating orphaned rows when deleting data from BlogCommentReply.
Your first DELETE from BlogCommentReply can be rewritten as two statements to make work correctly:
-- Remove replies added by the deleted user
DELETE
FROM dbo.BlogCommentReply
WHERE (UserId = #a_UserId)
-- Remove blog comment replies added by other users creating replies to a comment made by the deleted user
DELETE
FROM dbo.BlogCommentReply
WHERE (BlogCommentId IN (
SELECT BlogCommentId
FROM BlogComment
WHERE (UserId = #a_UserId)
))
In the first statement you are simply trying to delete rows from BlogCommentReply that use a FK to the User table.
In the second statement you delete any reply to a blog comment that will be deleted.
In response to your specific questions:
1. Did I create the table structure correctly?
It looks fine although there are several ways to achieve the same.
2. Do I need cascades - why?
No. Cascade deletes reek of poor design and lazy development. Try to
avoid them (note this is an opinion not a statement of fact).
There are some interesting points in this question.
3. Is the delete code order correct?
Yes, delete from the tables in order of key precidence.
4. I am of the belief that the parent table cannot be deleted until the children are deleted first. Is that correct?
Yes indeed, without cascaded deletes that is correct and, in your
case, the error thrown shows that the referential integrity of the
database is sound.
In the image, the Foreign Key Relationships window is showing the FK for the itemModifier_Rel table while on the object explorer it doesn't shows the same results.
What are the differences between these?
I'm having a problem of duplicated keys, some DBA have a wrong process that causes this and I'm trying to fix it.
I deleted the duplicates but now I'm seeing this. Although the resulting CREATE TABLE script for the table shows just 3 FK while on the Foreign Key Relationships I'm seeing 6
Here's the table's script after dropping the duplicated FK
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[itemModifier_Rel](
[ItemModifierTypeID] [bigint] NOT NULL,
[ItemID] [bigint] NOT NULL,
[ModifierItemID] [bigint] NULL,
[ModifierSequenceID] [bigint] NULL,
PRIMARY KEY NONCLUSTERED
(
[ItemModifierTypeID] ASC,
[ItemID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[itemModifier_Rel] WITH CHECK ADD FOREIGN KEY([ItemID])
REFERENCES [dbo].[item] ([ItemID])
GO
ALTER TABLE [dbo].[itemModifier_Rel] WITH CHECK ADD FOREIGN KEY([ItemModifierTypeID])
REFERENCES [dbo].[itemModifierType_Cat] ([ItemModifierTypeID])
GO
ALTER TABLE [dbo].[itemModifier_Rel] WITH CHECK ADD FOREIGN KEY([ModifierItemID])
REFERENCES [dbo].[item] ([ItemID])
GO
On left you see the keys constraint names under Keys. On right you have the dialog for setting up key properties visually. That dialog is listing all the foreign keys of your table plus all the foreign keys that reference to table. IOW you have 3 FK + 2 references to your table PK in that picture.
I originally had the following two tables.
tbl_Vehicle
VehicleID int identity
tbl_VehicleAssignment
RecordID int identity
VehicleID int FK
EmployeeID int
Original bit
When a vehicle comes in, it has an originally assignee, noted by the [Original] column in the second table above. Also set ON DELETE CASCADE, so if a vehicle is deleted, so are any associated assignment records. No problem there.
I decided this wasn't the best way to organize the data, and adding rules on the VehicleAssignment table to enforce only one vehicle can have a single 1 for Original seemed expensive in the long run. When I say that, I have several tables like this, some of which will grow quite large, so thinking of performance.
I then decided to change my tables like so:
tbl_Vehicle
VehicleID int identity
Orig_Assignment int NULL
tbl_VehicleAssignment
RecordID int identity
VehicleID int FK
EmployeeID int
The problem is, I then wanted to add a constraint so that if RecordID in tbl_VehicleAssignment was deleted, it set Orig_Assignment back to NULL.
Getting the following error:
Introducing FOREIGN KEY constraint 'FK_Orig_Vehicle_VehicleAssignment'
on table 'tbl_Vehicle' may cause cycles or multiple cascade paths.
Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other
FOREIGN KEY constraints.
But I'm not sure why exactly. If a vehicle is deleted, it cascades and deletes assignment records. But I'd also like to set [Orig_Assignment] to NULL when its associated record is deleted. I don't see how the two cross paths to cause multiple cascade paths. What's interesting though, is if I use the table designer wizard to create this, it actually saves the constraint in the tbl_VehicleAssignment table, but fails on the tbl_Vehicle table, and although closing the dialog and looking at the relationships in the tables look correct, there's obviously something wrong.
If I can't get around this, what would be the best method?
1. Trigger
I'd rather stay away from this
2. Another table for originals that allows nulls
So it would be like:
tbl_VehicleOriginal
VehicleID int
Orig_Assignment int NULL
Orig_SomethingElse int NULL
Orig_Etc. int NULL
This would create a row for every row in the tbl_Vehicle table.
3. Another table for originals but only when present
Such as:
tbl_VehicleOriginal
VehicleID int PK
RecordType PK
RecordID FK
I suppose my question is, why can't I just add the constraint? If it's not possible, what's the best way to organize the data?
UPDATE - Scripts to build the example
CREATE TABLE [dbo].[A_Vehicle](
[VehicleID] [int] IDENTITY(1,1) NOT NULL,
[Orig_Assignment] [int] NULL,
CONSTRAINT [PK_A_Vehicle] PRIMARY KEY CLUSTERED
(
[VehicleID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[A_VehicleAssignment](
[RecordID] [int] IDENTITY(1,1) NOT NULL,
[VehicleID] [int] NOT NULL,
CONSTRAINT [PK_A_VehicleAssignment] PRIMARY KEY CLUSTERED
(
[RecordID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[A_VehicleAssignment] WITH CHECK ADD CONSTRAINT [FK_A_VehicleAssignment_A_Vehicle] FOREIGN KEY([VehicleID])
REFERENCES [dbo].[A_Vehicle] ([VehicleID])
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[A_VehicleAssignment] CHECK CONSTRAINT [FK_A_VehicleAssignment_A_Vehicle]
GO
-- The below will fail
ALTER TABLE [dbo].[A_Vehicle] WITH CHECK ADD CONSTRAINT [FK_Orig_A_VehicleAssignment_A_Vehicle] FOREIGN KEY([Orig_Assignment])
REFERENCES [dbo].[A_VehicleAssignment] ([RecordID])
ON DELETE SET NULL
GO
SQL 2005, 600,000,000 rows.
I have a table called Location currently using the data type INT in identity PK column LocationID. I would like to attempt converting this data type to BIGINT.
The following script I think should help to allow inserted into the PK column but i am unsure how to progress form here.
SET IDENTITY_INSERT LOCATION ON /*allows insert into the identity column*/`
SET IDENTITY_INSERT LOCATION OFF /*Returns the identity column to initial state*/`
Location table create script below:
CREATE TABLE [dbo].[Location](
[LocationID] [int] IDENTITY(1,1) NOT NULL,
[JourneyID] [int] NULL,
[DeviceID] [int] NOT NULL,
[PacketTypeID] [int] NULL,
[PacketStatusID] [int] NULL,
CONSTRAINT [Location_PK] PRIMARY KEY CLUSTERED
(
[LocationID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[Location] WITH CHECK ADD CONSTRAINT [Device_Location_FK1] FOREIGN KEY([DeviceID])
REFERENCES [dbo].[Device] ([DeviceID])
GO
ALTER TABLE [dbo].[Location] CHECK CONSTRAINT [Device_Location_FK1]
GO
ALTER TABLE [dbo].[Location] WITH CHECK ADD CONSTRAINT [PacketStatus_Location_FK1] FOREIGN KEY([PacketStatusID])
REFERENCES [dbo].[PacketStatus] ([PacketStatusID])
GO
ALTER TABLE [dbo].[Location] CHECK CONSTRAINT [PacketStatus_Location_FK1]
GO
ALTER TABLE [dbo].[Location] WITH CHECK ADD CONSTRAINT [PacketType_Location_FK1] FOREIGN KEY([PacketTypeID])
REFERENCES [dbo].[PacketType] ([PacketTypeID])
GO
ALTER TABLE [dbo].[Location] CHECK CONSTRAINT [PacketType_Location_FK1]
One option i think would be to copy the data to a new table then delete the old table and rename the new one however we have constraints that will need to be dropped for this to work.
Your idea of a new table is the way to go.
On a development server, you can see the script that SSMS would produce if you change the data type using the table designer. It is a good start. This will add triggers and constraints back afterwards.
A tool like Red gate SQL Compare also allows you to check that everything was created OK
I'm putting together my own database and from examples I've seen, Foriegn Key can also be set as Primary Keys.
I was creating my Tables so that all of my FK were also PK. Is this wrong? When should a FK be a PK? Does it have to be a PK?
Primary Key's make sense in their own table... as the Id and Identity. But when using the Id is another table, does it have to be a PK as well?
A Foreign Key should only be the Primary Key when your trying to create a 1 to 1 or 1 to zero/1 mapping.
Example:
I have a Person table, an Employee table, and a Contractor table. All Employees are people, all Contractors are people and every Person is either an employee or a Contractor
Essentially you would end up with something like this.
In response to your people have multiple addresses you should create an association table. Here is a diagram.
As you can see now every person can have many addresses and since each Employee is a person then every Employee can have many addresses. This is the same for Contractor as well.
Edited: Here is the Change Script from SQL Server
BEGIN TRANSACTION
SET QUOTED_IDENTIFIER ON
SET ARITHABORT ON
SET NUMERIC_ROUNDABORT OFF
SET CONCAT_NULL_YIELDS_NULL ON
SET ANSI_NULLS ON
SET ANSI_PADDING ON
SET ANSI_WARNINGS ON
COMMIT
BEGIN TRANSACTION
GO
CREATE TABLE dbo.Address
(
AddressId bigint NOT NULL,
Address nvarchar(50) NULL,
City nvarchar(50) NULL,
State nvarchar(50) NULL
) ON [PRIMARY]
GO
ALTER TABLE dbo.Address ADD CONSTRAINT
PK_Address PRIMARY KEY CLUSTERED
(
AddressId
) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
ALTER TABLE dbo.Address SET (LOCK_ESCALATION = TABLE)
GO
COMMIT
BEGIN TRANSACTION
GO
CREATE TABLE dbo.Person
(
PersonId bigint NOT NULL,
Name nvarchar(50) NULL
) ON [PRIMARY]
GO
ALTER TABLE dbo.Person ADD CONSTRAINT
PK_Person PRIMARY KEY CLUSTERED
(
PersonId
) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
ALTER TABLE dbo.Person SET (LOCK_ESCALATION = TABLE)
GO
COMMIT
BEGIN TRANSACTION
GO
CREATE TABLE dbo.PersonAddress
(
PersonId bigint NOT NULL,
AddressId bigint NOT NULL
) ON [PRIMARY]
GO
ALTER TABLE dbo.PersonAddress ADD CONSTRAINT
PK_PersonAddress PRIMARY KEY CLUSTERED
(
PersonId,
AddressId
) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
ALTER TABLE dbo.PersonAddress ADD CONSTRAINT
FK_PersonAddress_Person FOREIGN KEY
(
PersonId
) REFERENCES dbo.Person
(
PersonId
) ON UPDATE NO ACTION
ON DELETE NO ACTION
GO
ALTER TABLE dbo.PersonAddress ADD CONSTRAINT
FK_PersonAddress_Address FOREIGN KEY
(
AddressId
) REFERENCES dbo.Address
(
AddressId
) ON UPDATE NO ACTION
ON DELETE NO ACTION
GO
ALTER TABLE dbo.PersonAddress SET (LOCK_ESCALATION = TABLE)
GO
COMMIT
BEGIN TRANSACTION
GO
CREATE TABLE dbo.Employee
(
EmployeeId bigint NOT NULL,
EmployeeNumber nvarchar(50) NULL
) ON [PRIMARY]
GO
ALTER TABLE dbo.Employee ADD CONSTRAINT
PK_Employee PRIMARY KEY CLUSTERED
(
EmployeeId
) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
ALTER TABLE dbo.Employee ADD CONSTRAINT
FK_Employee_Person FOREIGN KEY
(
EmployeeId
) REFERENCES dbo.Person
(
PersonId
) ON UPDATE NO ACTION
ON DELETE NO ACTION
GO
ALTER TABLE dbo.Employee SET (LOCK_ESCALATION = TABLE)
GO
COMMIT
BEGIN TRANSACTION
GO
CREATE TABLE dbo.Contractor
(
ContractorId bigint NOT NULL,
ContractorNumber nvarchar(50) NULL
) ON [PRIMARY]
GO
ALTER TABLE dbo.Contractor ADD CONSTRAINT
PK_Contractor PRIMARY KEY CLUSTERED
(
ContractorId
) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
ALTER TABLE dbo.Contractor ADD CONSTRAINT
FK_Contractor_Person FOREIGN KEY
(
ContractorId
) REFERENCES dbo.Person
(
PersonId
) ON UPDATE NO ACTION
ON DELETE NO ACTION
GO
ALTER TABLE dbo.Contractor SET (LOCK_ESCALATION = TABLE)
GO
COMMIT
There is only one scenarios that would require a FK to also be a PK.
When the table represents a subclass, or subset, of the things in the PK table, for example, Eg, SalariedEmployees table which has a FK to the Employees table...
A FK is a field pointing to the PK of another table. That's it.
A table linked to itself could contain a FK pointing to its own PK.
A PK that is also a FK can only happen in the child table of a 1 to 1 relationship.
An FK should only also be a PK if the two tables have a one-to-one relationship and the second table was added because the first was too wide and these were items not always needed in most queries.
It will not work at all if you have a one-to-many relationship or a many-to-many relationship.
FKs are much more often not also the PK. If I havea person table and a related address table, If I make the PK and the FK the same thing, then I can only store one address, but most address tables allow for mulitple addresses for the same person or organization. IN that case you would have and AddressID as the PK and a person_id as the FK to the person table. This is the most common PK/FK scenario.
One situation where a given column is both a PK and an FK is the relational model for the gen-spec design pattern. In one of the other responses "Employees" is a specialization of Persons". The PK in the employees tables references the PK in the Persons table. So the PK in the specialized table is also an FK.
This allows the creation of a view that joins employees and persons to provide in a single view all the data about employees, whether that data is peculiar to employees (like "Hire Date") or is common to all persons, whether or not they are employees (like "Date of Birth").
It is not good practice to make every PK also be an FK. The FKs should reflect the logical structure of the data. If the logical model is illogical, you're headed for trouble.