如何在 SQL Server 中创建一对一关系?
我了解当 DBMS 支持可延迟约束时,有两种主要方法可以“实现”1:1 关系。这两种方法都需要两个主表中 other-half 表的外键。
- 使用第三个表来前向声明 PK 值。
- 三个表:
-
只有 PK 值的转发声明表。
- 一个或多个具有 FK 到前向声明表的依赖表。
- 最终的主表,它对前向声明表和所有依赖表都有 FK。
- 这是最简单的实现方式,可能具有最佳的整体性能,但会使
INSERT 操作稍微复杂一些。
- 缺点是second表中存在行并不意味着主表行存在。
- 这与
IDENTITY 兼容,但IDENTITY 必须在前向声明表中,而不是在主表中。
- 允许半表一个中的外键列是
NULL,但只允许通过VIEW 访问数据,该VIEW 只公开具有有效(非@ 987654326@)。
- 您可以将
INSERT 直接支持到VIEW 中(这意味着它可以与大多数ORM 配合使用,包括实体框架)。
- 您还可以通过将其设为
INDEXED VIEW 来提高性能。
选项 1:使用第三个表进行前向声明:
通过将系统视为不可变数据场景,我指出了与构建对象图的相似之处(并且在图中的节点之间具有任意关系,甚至是 1:1)。
通过添加(可选隐藏)第三个表和提供值的“前向声明”的视图,然后 可以被相关表引用,而“真正的”主表对(可选隐藏的)列有约束。
使用 OP 的示例,这是一个 1:1 :
CREATE TABLE dbo.CountryDecl (
CountryName nvarchar(255) NOT NULL,
CONSTRAINT PK_CountryDecl PRIMARY KEY ( CountryName )
);
CREATE TABLE dbo.Countries (
CountryName nvarchar(255) NOT NULL,
CapitalName nvarchar(255) NOT NULL,
Inhabitants bigint NOT NULL,
AreaKM2 bigint NOT NULL,
CONSTRAINT PK_Countries PRIMARY KEY ( CountryName ),
CONSTRAINT FK_CountriesDecl FOREIGN KEY ( CountryName ) REFERENCES dbo.CountryDecl ( CountryName ),
CONSTRAINT FK_Countries_Capitals FOREIGN KEY ( CountryName ) REFERENCES dbo.Capitals ( CountryName )
);
CREATE TABLE dbo.Capitals (
CountryName nvarchar(255) NOT NULL,
CapitalName nvarchar(255) NOT NULL,
Inhabitants bigint NOT NULL,
AreaKM2 int NOT NULL,
CONSTRAINT PK_Capitals PRIMARY KEY ( CountryName ),
CONSTRAINT FK_CountriesDecl FOREIGN KEY ( CountryName ) REFERENCES dbo.CountryDecl ( CountryName )
)
INSERT 操作如下所示:
INSERT INTO dbo.CountryDecl ( CountryName ) VALUES ( 'Kazakhstan' );
INSERT INTO dbo.Capitals ( CountryName, CapitalName, Inhabitants, Area ) VALUES ( 'Kazakhstan', 'Nur-Sultan', 1136008, 810 );
INSERT INTO dbo.Countries ( CountryName, CapitalName, Inhabitants, Area ) VALUES ( 'Kazakhstan', 'Nur-Sultan', 18711560, 2724900 );
保证dbo.Countries 的所有查询总是返回有效行(即,如果dbo.Countries 中有一行,那么dbo.Capitals 中也总是有一个有效行),但这不是传递性的:一行在dbo.Capitals 中不保证dbo.Countries 中存在一行。
选项 2a:使用VIEW 作为隐藏无效行的幕布(使用NULLable FK):
CREATE TABLE dbo.CountriesData (
CountryName nvarchar(255) NOT NULL,
CapitalName nvarchar(255) NULL,
Inhabitants bigint NOT NULL,
AreaKM2 bigint NOT NULL,
CONSTRAINT PK_Countries PRIMARY KEY ( CountryName ),
CONSTRAINT FK_Countries_Capitals FOREIGN KEY ( CountryName ) REFERENCES dbo.Capitals ( CountryName )
);
CREATE TABLE dbo.Capitals (
CountryName nvarchar(255) NOT NULL,
CapitalName nvarchar(255) NOT NULL,
Inhabitants bigint NULL, /* Inhabitants and AreaKM2 need to be NULL as INSERTing into dbo.CountriesData means a dbo.Capitals row will be created, but we don't have any values for these columns at-that-point, but if you have that data then these columns *can* be NOT NULL. So this is not a limitation of this approach, only your domain-model. */
AreaKM2 int NULL,
CONSTRAINT PK_Capitals PRIMARY KEY ( CountryName ),
CONSTRAINT FK_CountriesDecl FOREIGN KEY ( CountryName ) REFERENCES dbo.CountryDecl ( CountryName )
);
CREATE VIEW dbo.Countries AS
SELECT
CountryName,
CapitalName,
Inhabitants,
AreaKM2
FROM
dbo.CountriesData
WHERE
CapitalName IS NOT NULL;
CREATE TRIGGER TR_Countries_INSERT ON dbo.Countries INSTEAD OF INSERT AS
BEGIN
SET NOCOUNT ON;
INSERT INTO dbo.CountriesData ( CountryName, CapitalName, Inhabitants, AreaKM2 )
SELECT CountryName, NULL, Inhabitants, AreaKM2 FROM [inserted];
INSERT INTO dbo.Capitals ( CountryName, CapitalName, Inhabitants, AreaKM2 )
SELECT CountryName, CapitalName, NULL, NULL FROM [inserted];
UPDATE
cd
SET
cd.CapitalName = ic.CapitalName
FROM
dbo.CountriesData AS cd
INNER JOIN [inserted] AS ic ON ic.CountryName = cd.CountryName
WHERE
cd.CapitalName IS NULL;
END
选项 2b:使用VIEW 作为隐藏无效行的幕布(没有NULLable 列,但在VIEW 中使用JOIN):
CREATE TABLE dbo.CountriesData (
CountryName nvarchar(255) NOT NULL, /* NOTE: there is no CapitalName column */
Inhabitants bigint NOT NULL,
AreaKM2 bigint NOT NULL,
CONSTRAINT PK_Countries PRIMARY KEY ( CountryName ),
CONSTRAINT FK_Countries_Capitals FOREIGN KEY ( CountryName ) REFERENCES dbo.Capitals ( CountryName )
);
CREATE TABLE dbo.Capitals (
CountryName nvarchar(255) NOT NULL,
CapitalName nvarchar(255) NOT NULL,
Inhabitants bigint NULL,
AreaKM2 int NULL,
CONSTRAINT PK_Capitals PRIMARY KEY ( CountryName ),
CONSTRAINT FK_CountriesDecl FOREIGN KEY ( CountryName ) REFERENCES dbo.CountryDecl ( CountryName )
);
CREATE VIEW dbo.Countries AS
SELECT
d.CountryName,
c.CapitalName,
d.Inhabitants,
d.AreaKM2
FROM
dbo.CountriesData AS d
INNER JOIN dbo.Capitals AS c ON d.CountryName = c.CountryName;
CREATE TRIGGER TR_Countries_INSERT ON dbo.Countries INSTEAD OF INSERT AS
BEGIN
SET NOCOUNT ON;
INSERT INTO dbo.CountriesData ( CountryName, Inhabitants, AreaKM2 )
SELECT CountryName, Inhabitants, AreaKM2 FROM [inserted];
INSERT INTO dbo.Capitals ( CountryName, CapitalName, Inhabitants, AreaKM2 )
SELECT CountryName, CapitalName, NULL, NULL FROM [inserted];
END