gpt4 book ai didi

sql - 强制多个列之间的相互唯一性

转载 作者:行者123 更新时间:2023-12-02 00:53:19 28 4
gpt4 key购买 nike

我正在尝试找到一种直观的方法来强制表中两列的相互唯一性。我并不是在寻找复合唯一性,即不允许重复的键组合。相反,我想要一个规则,其中任何键都不能再次出现在任一列中。举个例子:

CREATE TABLE Rooms
(
Id INT NOT NULL PRIMARY KEY,
)

CREATE TABLE Occupants
(
PersonName VARCHAR(20),
LivingRoomId INT NULL REFERENCES Rooms (Id),
DiningRoomId INT NULL REFERENCES Rooms (Id),
)

一个人可以选择任何房间作为客厅,任何其他房间作为餐厅。房间一旦分配给一个居住者,就不能再次分配给另一个人(无论是作为客厅还是作为餐厅)。

我知道这个问题可以通过数据标准化来解决;但是,我无法 更改架构 对架构进行重大更改。

更新:针对建议的答案:

两个唯一约束(或两个唯一索引)不会阻止两列之间的重复。同样,一个简单的LivingRoomId != DiningRoomId检查约束不会防止跨行重复。例如,我希望禁止以下数据:

INSERT INTO Rooms VALUES (1), (2), (3), (4)
INSERT INTO Occupants VALUES ('Alex', 1, 2)
INSERT INTO Occupants VALUES ('Lincoln', 2, 3)

2号房间同时被亚历克斯(作为客厅)和林肯(作为餐厅)占用;这是不应该允许的。

更新2:我对三个主要的建议解决方案进行了一些测试,计算了将 500,000 行插入 Occupants 需要多长时间。表,每行都有一对随机的唯一房间 ID。

扩展Occupants具有唯一索引和检查约束(调用标量函数)的表会导致插入花费大约三倍的时间。标量函数的实现不完整,仅检查新住户的起居室是否与现有住户的餐厅冲突。如果同时执行反向检查,我无法在合理的时间内完成插入。

添加一个触发器,将每个占用者的房间作为新行插入到另一个表中,会使性能降低 48%。同样,索引 View 花费的时间要长 43%。在我看来,使用索引 View 更干净,因为它避免了创建另一个表的需要,并且允许 SQL Server 自动处理更新和删除。

完整的脚本和测试结果如下:

SET STATISTICS TIME OFF
SET NOCOUNT ON

CREATE TABLE Rooms
(
Id INT NOT NULL PRIMARY KEY IDENTITY(1,1),
RoomName VARCHAR(10),
)

CREATE TABLE Occupants
(
Id INT NOT NULL PRIMARY KEY IDENTITY(1,1),
PersonName VARCHAR(10),
LivingRoomId INT NOT NULL REFERENCES Rooms (Id),
DiningRoomId INT NOT NULL REFERENCES Rooms (Id)
)

GO

DECLARE @Iterator INT = 0
WHILE (@Iterator < 10)
BEGIN
INSERT INTO Rooms
SELECT TOP (1000000) 'ABC'
FROM sys.all_objects s1 WITH (NOLOCK)
CROSS JOIN sys.all_objects s2 WITH (NOLOCK)
CROSS JOIN sys.all_objects s3 WITH (NOLOCK);
SET @Iterator = @Iterator + 1
END;

DECLARE @RoomsCount INT = (SELECT COUNT(*) FROM Rooms);

SELECT TOP 1000000 RoomId
INTO ##RandomRooms
FROM
(
SELECT DISTINCT
CAST(RAND(CHECKSUM(NEWID())) * @RoomsCount AS INT) + 1 AS RoomId
FROM sys.all_objects s1 WITH (NOLOCK)
CROSS JOIN sys.all_objects s2 WITH (NOLOCK)

) s

ALTER TABLE ##RandomRooms
ADD Id INT IDENTITY(1,1)

SELECT
'XYZ' AS PersonName,
R1.RoomId AS LivingRoomId,
R2.RoomId AS DiningRoomId
INTO ##RandomOccupants
FROM ##RandomRooms R1
JOIN ##RandomRooms R2
ON R2.Id % 2 = 0
AND R2.Id = R1.Id + 1

GO

PRINT CHAR(10) + 'Test 1: No integrity check'

CHECKPOINT;
DBCC FREEPROCCACHE WITH NO_INFOMSGS;
DBCC DROPCLEANBUFFERS WITH NO_INFOMSGS;
SET NOCOUNT OFF
SET STATISTICS TIME ON

INSERT INTO Occupants
SELECT *
FROM ##RandomOccupants

SET STATISTICS TIME OFF
SET NOCOUNT ON

TRUNCATE TABLE Occupants

PRINT CHAR(10) + 'Test 2: Unique indexes and check constraint'

CREATE UNIQUE INDEX UQ_LivingRoomId
ON Occupants (LivingRoomId)

CREATE UNIQUE INDEX UQ_DiningRoomId
ON Occupants (DiningRoomId)

GO

CREATE FUNCTION CheckExclusiveRoom(@occupantId INT)
RETURNS BIT AS
BEGIN
RETURN
(
SELECT CASE WHEN EXISTS
(
SELECT *
FROM Occupants O1
JOIN Occupants O2
ON O1.LivingRoomId = O2.DiningRoomId
-- OR O1.DiningRoomId = O2.LivingRoomId
WHERE O1.Id = @occupantId
)
THEN 0
ELSE 1
END
)
END

GO

ALTER TABLE Occupants
ADD CONSTRAINT ExclusiveRoom
CHECK (dbo.CheckExclusiveRoom(Id) = 1)

CHECKPOINT;
DBCC FREEPROCCACHE WITH NO_INFOMSGS;
DBCC DROPCLEANBUFFERS WITH NO_INFOMSGS;
SET NOCOUNT OFF
SET STATISTICS TIME ON

INSERT INTO Occupants
SELECT *
FROM ##RandomOccupants

SET STATISTICS TIME OFF
SET NOCOUNT ON

ALTER TABLE Occupants DROP CONSTRAINT ExclusiveRoom
DROP INDEX UQ_LivingRoomId ON Occupants
DROP INDEX UQ_DiningRoomId ON Occupants
DROP FUNCTION CheckExclusiveRoom

TRUNCATE TABLE Occupants

PRINT CHAR(10) + 'Test 3: Insert trigger'

CREATE TABLE RoomTaken
(
RoomId INT NOT NULL PRIMARY KEY REFERENCES Rooms (Id)
)

GO

CREATE TRIGGER UpdateRoomTaken
ON Occupants
AFTER INSERT
AS
INSERT INTO RoomTaken
SELECT RoomId
FROM
(
SELECT LivingRoomId AS RoomId
FROM INSERTED
UNION ALL
SELECT DiningRoomId AS RoomId
FROM INSERTED
) s

GO

CHECKPOINT;
DBCC FREEPROCCACHE WITH NO_INFOMSGS;
DBCC DROPCLEANBUFFERS WITH NO_INFOMSGS;
SET NOCOUNT OFF
SET STATISTICS TIME ON

INSERT INTO Occupants
SELECT *
FROM ##RandomOccupants

SET STATISTICS TIME OFF
SET NOCOUNT ON

DROP TRIGGER UpdateRoomTaken
DROP TABLE RoomTaken

TRUNCATE TABLE Occupants

PRINT CHAR(10) + 'Test 4: Indexed view with unique index'

CREATE TABLE TwoRows
(
Id INT NOT NULL PRIMARY KEY
)

INSERT INTO TwoRows VALUES (1), (2)

GO

CREATE VIEW OccupiedRooms
WITH SCHEMABINDING
AS
SELECT RoomId = CASE R.Id WHEN 1
THEN O.LivingRoomId
ELSE O.DiningRoomId
END
FROM dbo.Occupants O
CROSS JOIN dbo.TwoRows R

GO

CREATE UNIQUE CLUSTERED INDEX UQ_OccupiedRooms
ON OccupiedRooms (RoomId);

CHECKPOINT;
DBCC FREEPROCCACHE WITH NO_INFOMSGS;
DBCC DROPCLEANBUFFERS WITH NO_INFOMSGS;
SET NOCOUNT OFF
SET STATISTICS TIME ON

INSERT INTO Occupants
SELECT *
FROM ##RandomOccupants

SET STATISTICS TIME OFF
SET NOCOUNT ON

DROP INDEX UQ_OccupiedRooms ON OccupiedRooms
DROP VIEW OccupiedRooms
DROP TABLE TwoRows

TRUNCATE TABLE Occupants

DROP TABLE ##RandomRooms
DROP TABLE ##RandomOccupants

DROP TABLE Occupants
DROP TABLE Rooms


/* Results:

Test 1: No integrity check

SQL Server Execution Times:
CPU time = 5210 ms, elapsed time = 10853 ms.

(500000 row(s) affected)

Test 2: Unique indexes and check constraint

SQL Server Execution Times:
CPU time = 21996 ms, elapsed time = 27019 ms.

(500000 row(s) affected)

Test 3: Insert trigger
SQL Server parse and compile time:
CPU time = 5663 ms, elapsed time = 11192 ms.

SQL Server Execution Times:
CPU time = 4914 ms, elapsed time = 4913 ms.

(1000000 row(s) affected)

SQL Server Execution Times:
CPU time = 10577 ms, elapsed time = 16105 ms.

(500000 row(s) affected)

Test 4: Indexed view with unique index

SQL Server Execution Times:
CPU time = 10171 ms, elapsed time = 15777 ms.

(500000 row(s) affected)

*/

最佳答案

我认为做到这一点的唯一方法是使用约束和函数。

伪代码(很久没有这样做了):

CREATE FUNCTION CheckExlusiveRoom
RETURNS bit
declare @retval bit
set @retval = 0
select retval = 1
from Occupants as Primary
join Occupants as Secondary
on Primary.LivingRoomId = Secondary.DiningRoomId
where Primary.ID <> Secondary.ID
or ( Primary.DiningRoomId= Secondary.DiningRoomId
or Primary.LivingRoomId = Secondary.LivingRoomID)
return @retval
GO

然后,在检查约束中使用此函数......

替代方案是使用中间表 OccupiedRoom,您总是会在其中插入使用的房间(例如通过触发器?)和 FK,而不是房间表

对评论的 react :

您是否需要直接在表上强制执行它,或者是否在插入/更新足够的 react 时发生了约束违规?因为那时我是这样想的:

  1. 创建一个简单的表:

    create table RoomTaken (RoomID int primary key references Room (Id) )
  2. 在插入/更新/删除时创建触发器,以确保占用者中使用的任何房间也保留在 RoomID 中。

  3. 如果您尝试重复房间使用情况,RoomTaken 表将引发 PK 违规

不确定这是否足够和/或它与 UDF 的速度比较如何(我认为它会更好)。

是的,我看到 RoomTaken 不会 FK 到 Occupants 中的使用的问题,但是...实际上,您在一些限制下工作并且没有完美的解决方案 - 这是速度 (UDF) 与 100% 完整性执行我认为。

关于sql - 强制多个列之间的相互唯一性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24562733/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com