约束多个表。自定义函数?
Constraint with multiple tables. UDF?
我有两个 table:table A 和 table B。这两个 table 与 A 中的主键和 A 中的外键相关联B.
Table答:
CREATE TABLE [BIO].[table_A](
[table_A_id] [int] IDENTITY(1,1) NOT NULL,
[type_id] [nvarchar](2) NOT NULL
CONSTRAINT [PK_table_A] PRIMARY KEY CLUSTERED
(
[table_A_id] ASC
))
Table乙:
CREATE TABLE [BIO].[table_B](
[table_B_id] [int] IDENTITY(1,1) NOT NULL,
[table_A_id] [int] NOT NULL,
[analysis_id] [tinyint] NOT NULL
CONSTRAINT [PK_table_B] PRIMARY KEY CLUSTERED
(
[table_B_id] ASC
))
ALTER TABLE [BIO].[table_B] WITH CHECK
ADD CONSTRAINT [FK_table_B_table_A] FOREIGN KEY([table_A_id])
REFERENCES [BIO].[table_A] ([table_A_id])
GO
ALTER TABLE [BIO].[table_B] CHECK CONSTRAINT [FK_table_B_table_A]
GO
Table B 必须仅包含根据 table A 中的值的特定值。
比如我在tableA里有BL,我在tableB里只能有1个或3个;如果我在 table A 中有 ST,我在 table B 中只能有 2 或 4。
我已经设置了一个桥 table 来定义这些组合:BL→1 或 3,ST→2 或 4。
桥table:
CREATE TABLE [QRY].[bridge_table](
[type_id] [nvarchar](2) NOT NULL,
[analysis_id] [tinyint] NOT NULL,
CONSTRAINT [PK_bridge_table] PRIMARY KEY CLUSTERED
(
[type_id] ASC,
[analysis_id] ASC
))
我目前对每个 table 使用一个约束,以确保根据桥 table 中定义的组合,任何插入或更新都是正确的。这两个约束基于 UDF。
对 table B 的约束:
ALTER TABLE [BIO].[Table_B] WITH CHECK
ADD CONSTRAINT [CK_chkAnalysisType]
CHECK (([QRY].[TypeAnalysisMatch_table_B]([table_A_id])>(0)))
GO
UDF:
CREATE FUNCTION [QRY].[TypeAnalysisMatch_table_B] (@table_A_id int)
RETURNS int
AS
BEGIN
RETURN
(
SELECT
Count(BIO.table_A.table_A_id) AS cnt_rec
FROM
QRY.bridge_table
INNER JOIN BIO.table_A ON QRY.bridge_table.type_id = BIO.table_A.type_id
INNER JOIN BIO.table_B ON
QRY.bridge_table.analysis_id = BIO.table_B.analysis_id
AND BIO.table_A.table_A_id = BIO.table_B.table_A_id
WHERE
BIO.table_A.table_A_id = @table_A_id
)
END
它适用于 INSERT
但不适用于 UPDATE
。此外,当我读到应该避免约束中的 UDF 时,我正在寻找更好的解决方案。
什么是这些限制的有效替代方案?
你是对的,在 CHECK
约束中使用 UDF
可能会很棘手,一些 UPDATE
语句可能会绕过检查:
MSSQL: Update statement avoiding the CHECK constraint
正如您在该 SO 问题中所见,建议使用触发器进行检查的答案。编写一个正确的高效触发器也不是一件容易的事。
我假设您的 bridge_table
包含以下数据:
type_id analysis_id
BL 1
BL 3
ST 2
ST 4
我将仅使用外键设置这些约束,而不使用 UDF。不过,它需要一些(最少的)数据重复。我假设真实的 table_A
和 table_B
比这个例子有更多的列。
1.在table_A
主键中包含type_id
:
CREATE TABLE [table_A](
[table_A_id] [int] IDENTITY(1,1) NOT NULL,
[type_id] [nvarchar](2) NOT NULL,
CONSTRAINT [PK_table_A] PRIMARY KEY CLUSTERED
(
[table_A_id] ASC,
[type_id] ASC
))
2. 在table_B
中添加一列type_id
。是的,与您在 table_A
中已有的列相同。这就是我上面提到的重复数据:
CREATE TABLE [table_B](
[table_B_id] [int] IDENTITY(1,1) NOT NULL,
[table_A_id] [int] NOT NULL,
[type_id] [nvarchar](2) NOT NULL,
[analysis_id] [tinyint] NOT NULL
CONSTRAINT [PK_table_B] PRIMARY KEY CLUSTERED
(
[table_B_id] ASC
))
3. 在两列上创建链接 table_B
和 table_A
的外键 (table_A_id, type_id)
:
ALTER TABLE [table_B] WITH CHECK
ADD CONSTRAINT [FK_table_B_table_A] FOREIGN KEY([table_A_id], [type_id])
REFERENCES [table_A] ([table_A_id], [type_id])
此约束保证 table_B
中重复的 type_id
值将与 table_A
.
中的原始值一致
4. 在两列上再次创建链接 table_B
和 bridge_table
的外键 (type_id, analysis_id)
:
ALTER TABLE [table_B] WITH CHECK
ADD CONSTRAINT [FK_table_B_bridge_table] FOREIGN KEY([type_id], [analysis_id])
REFERENCES [bridge_table] ([type_id], [analysis_id])
5. 现在您可以测试一切是否按预期工作。
向 table_A
添加几行:
INSERT INTO [table_A] ([type_id])
VALUES ('BL'), ('BL'), ('ST'), ('ZZ');
table_A_id type_id
1 BL
2 BL
3 ST
4 ZZ
尝试将有效数据插入table_B
:
INSERT INTO [dbo].[table_B] ([table_A_id],[type_id],[analysis_id])
VALUES (1,'BL',1)
INSERT INTO [dbo].[table_B] ([table_A_id],[type_id],[analysis_id])
VALUES (1,'BL',3)
INSERT INTO [dbo].[table_B] ([table_A_id],[type_id],[analysis_id])
VALUES (2,'BL',3)
INSERT INTO [dbo].[table_B] ([table_A_id],[type_id],[analysis_id])
VALUES (3,'ST',2)
INSERT INTO [dbo].[table_B] ([table_A_id],[type_id],[analysis_id])
VALUES (3,'ST',2)
table_B_id table_A_id type_id analysis_id
1 1 BL 1
2 1 BL 3
3 2 BL 3
4 3 ST 2
5 3 ST 2
尝试插入无效数据:
INSERT INTO [dbo].[table_B] ([table_A_id],[type_id],[analysis_id])
VALUES (3,'ST',1)
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_table_B_bridge_table".
The conflict occurred in database "tempdb", table "dbo.bridge_table".
The statement has been terminated.
也就是说,ST
不能有 analysis_id=1
INSERT INTO [dbo].[table_B] ([table_A_id],[type_id],[analysis_id])
VALUES (3,'BL',1)
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_table_B_table_A".
The conflict occurred in database "tempdb", table "dbo.table_A".
The statement has been terminated.
这意味着 table_A
中带有 table_A_id=3
的行在 type_id
中没有 BL
。
外键也会继续检查所有 UPDATE
语句的数据一致性:
UPDATE [dbo].[table_B]
SET [type_id] = 'ST'
WHERE [table_B_id] = 1
The UPDATE statement conflicted with the FOREIGN KEY constraint "FK_table_B_table_A".
The conflict occurred in database "tempdb", table "dbo.table_A".
The statement has been terminated.
UPDATE [dbo].[table_B]
SET [analysis_id] = 2
WHERE [table_B_id] = 1
The UPDATE statement conflicted with the FOREIGN KEY constraint "FK_table_B_bridge_table".
The conflict occurred in database "tempdb", table "dbo.bridge_table".
The statement has been terminated.
但是这个有效:
UPDATE [dbo].[table_B]
SET [analysis_id] = 3
WHERE [table_B_id] = 1
(1 row(s) affected)
我有两个 table:table A 和 table B。这两个 table 与 A 中的主键和 A 中的外键相关联B.
Table答:
CREATE TABLE [BIO].[table_A](
[table_A_id] [int] IDENTITY(1,1) NOT NULL,
[type_id] [nvarchar](2) NOT NULL
CONSTRAINT [PK_table_A] PRIMARY KEY CLUSTERED
(
[table_A_id] ASC
))
Table乙:
CREATE TABLE [BIO].[table_B](
[table_B_id] [int] IDENTITY(1,1) NOT NULL,
[table_A_id] [int] NOT NULL,
[analysis_id] [tinyint] NOT NULL
CONSTRAINT [PK_table_B] PRIMARY KEY CLUSTERED
(
[table_B_id] ASC
))
ALTER TABLE [BIO].[table_B] WITH CHECK
ADD CONSTRAINT [FK_table_B_table_A] FOREIGN KEY([table_A_id])
REFERENCES [BIO].[table_A] ([table_A_id])
GO
ALTER TABLE [BIO].[table_B] CHECK CONSTRAINT [FK_table_B_table_A]
GO
Table B 必须仅包含根据 table A 中的值的特定值。 比如我在tableA里有BL,我在tableB里只能有1个或3个;如果我在 table A 中有 ST,我在 table B 中只能有 2 或 4。
我已经设置了一个桥 table 来定义这些组合:BL→1 或 3,ST→2 或 4。
桥table:
CREATE TABLE [QRY].[bridge_table](
[type_id] [nvarchar](2) NOT NULL,
[analysis_id] [tinyint] NOT NULL,
CONSTRAINT [PK_bridge_table] PRIMARY KEY CLUSTERED
(
[type_id] ASC,
[analysis_id] ASC
))
我目前对每个 table 使用一个约束,以确保根据桥 table 中定义的组合,任何插入或更新都是正确的。这两个约束基于 UDF。
对 table B 的约束:
ALTER TABLE [BIO].[Table_B] WITH CHECK
ADD CONSTRAINT [CK_chkAnalysisType]
CHECK (([QRY].[TypeAnalysisMatch_table_B]([table_A_id])>(0)))
GO
UDF:
CREATE FUNCTION [QRY].[TypeAnalysisMatch_table_B] (@table_A_id int)
RETURNS int
AS
BEGIN
RETURN
(
SELECT
Count(BIO.table_A.table_A_id) AS cnt_rec
FROM
QRY.bridge_table
INNER JOIN BIO.table_A ON QRY.bridge_table.type_id = BIO.table_A.type_id
INNER JOIN BIO.table_B ON
QRY.bridge_table.analysis_id = BIO.table_B.analysis_id
AND BIO.table_A.table_A_id = BIO.table_B.table_A_id
WHERE
BIO.table_A.table_A_id = @table_A_id
)
END
它适用于 INSERT
但不适用于 UPDATE
。此外,当我读到应该避免约束中的 UDF 时,我正在寻找更好的解决方案。
什么是这些限制的有效替代方案?
你是对的,在 CHECK
约束中使用 UDF
可能会很棘手,一些 UPDATE
语句可能会绕过检查:
MSSQL: Update statement avoiding the CHECK constraint
正如您在该 SO 问题中所见,建议使用触发器进行检查的答案。编写一个正确的高效触发器也不是一件容易的事。
我假设您的 bridge_table
包含以下数据:
type_id analysis_id
BL 1
BL 3
ST 2
ST 4
我将仅使用外键设置这些约束,而不使用 UDF。不过,它需要一些(最少的)数据重复。我假设真实的 table_A
和 table_B
比这个例子有更多的列。
1.在table_A
主键中包含type_id
:
CREATE TABLE [table_A](
[table_A_id] [int] IDENTITY(1,1) NOT NULL,
[type_id] [nvarchar](2) NOT NULL,
CONSTRAINT [PK_table_A] PRIMARY KEY CLUSTERED
(
[table_A_id] ASC,
[type_id] ASC
))
2. 在table_B
中添加一列type_id
。是的,与您在 table_A
中已有的列相同。这就是我上面提到的重复数据:
CREATE TABLE [table_B](
[table_B_id] [int] IDENTITY(1,1) NOT NULL,
[table_A_id] [int] NOT NULL,
[type_id] [nvarchar](2) NOT NULL,
[analysis_id] [tinyint] NOT NULL
CONSTRAINT [PK_table_B] PRIMARY KEY CLUSTERED
(
[table_B_id] ASC
))
3. 在两列上创建链接 table_B
和 table_A
的外键 (table_A_id, type_id)
:
ALTER TABLE [table_B] WITH CHECK
ADD CONSTRAINT [FK_table_B_table_A] FOREIGN KEY([table_A_id], [type_id])
REFERENCES [table_A] ([table_A_id], [type_id])
此约束保证 table_B
中重复的 type_id
值将与 table_A
.
4. 在两列上再次创建链接 table_B
和 bridge_table
的外键 (type_id, analysis_id)
:
ALTER TABLE [table_B] WITH CHECK
ADD CONSTRAINT [FK_table_B_bridge_table] FOREIGN KEY([type_id], [analysis_id])
REFERENCES [bridge_table] ([type_id], [analysis_id])
5. 现在您可以测试一切是否按预期工作。
向 table_A
添加几行:
INSERT INTO [table_A] ([type_id])
VALUES ('BL'), ('BL'), ('ST'), ('ZZ');
table_A_id type_id
1 BL
2 BL
3 ST
4 ZZ
尝试将有效数据插入table_B
:
INSERT INTO [dbo].[table_B] ([table_A_id],[type_id],[analysis_id])
VALUES (1,'BL',1)
INSERT INTO [dbo].[table_B] ([table_A_id],[type_id],[analysis_id])
VALUES (1,'BL',3)
INSERT INTO [dbo].[table_B] ([table_A_id],[type_id],[analysis_id])
VALUES (2,'BL',3)
INSERT INTO [dbo].[table_B] ([table_A_id],[type_id],[analysis_id])
VALUES (3,'ST',2)
INSERT INTO [dbo].[table_B] ([table_A_id],[type_id],[analysis_id])
VALUES (3,'ST',2)
table_B_id table_A_id type_id analysis_id
1 1 BL 1
2 1 BL 3
3 2 BL 3
4 3 ST 2
5 3 ST 2
尝试插入无效数据:
INSERT INTO [dbo].[table_B] ([table_A_id],[type_id],[analysis_id])
VALUES (3,'ST',1)
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_table_B_bridge_table".
The conflict occurred in database "tempdb", table "dbo.bridge_table".
The statement has been terminated.
也就是说,ST
不能有 analysis_id=1
INSERT INTO [dbo].[table_B] ([table_A_id],[type_id],[analysis_id])
VALUES (3,'BL',1)
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_table_B_table_A".
The conflict occurred in database "tempdb", table "dbo.table_A".
The statement has been terminated.
这意味着 table_A
中带有 table_A_id=3
的行在 type_id
中没有 BL
。
外键也会继续检查所有 UPDATE
语句的数据一致性:
UPDATE [dbo].[table_B]
SET [type_id] = 'ST'
WHERE [table_B_id] = 1
The UPDATE statement conflicted with the FOREIGN KEY constraint "FK_table_B_table_A".
The conflict occurred in database "tempdb", table "dbo.table_A".
The statement has been terminated.
UPDATE [dbo].[table_B]
SET [analysis_id] = 2
WHERE [table_B_id] = 1
The UPDATE statement conflicted with the FOREIGN KEY constraint "FK_table_B_bridge_table".
The conflict occurred in database "tempdb", table "dbo.bridge_table".
The statement has been terminated.
但是这个有效:
UPDATE [dbo].[table_B]
SET [analysis_id] = 3
WHERE [table_B_id] = 1
(1 row(s) affected)