我正在尝试跨多个表创建一个唯一约束。我在这里找到了类似的问题,但是他们并没有完全捕捉到我想要做的精神。
举个例子,我有三个表,t_Analog,t_Discrete,t_Message
CREATE TABLE t_Analog(
[AppName] [nvarchar](20) NOT NULL,
[ItemName] [nvarchar](32) NOT NULL,
[Value] [float] NOT NULL,
CONSTRAINT [uc_t_Analog] UNIQUE(AppName, ItemName)
)
CREATE TABLE t_Discrete(
[AppName] [nvarchar](20) NOT NULL,
[ItemName] [nvarchar](32) NOT NULL,
[Value] [bit] NOT NULL,
CONSTRAINT [uc_t_Discrete] UNIQUE(AppName, ItemName)
)
CREATE TABLE t_Message(
[AppName] [nvarchar](20) NOT NULL,
[ItemName] [nvarchar](32) NOT NULL,
[Value] [nvarchar](256) NOT NULL,
CONSTRAINT [uc_t_Message] UNIQUE(AppName, ItemName)
)
我的目标是让所有3个表中的AppName和ItemName都是唯一的。例如,应用程序X中的项目名称Y不能同时存在于模拟和离散表中。
请注意,这个例子是设计的,每个Type的实际数据都不同,大到足以组合表并添加一个非常难看的Type列。
如果您对此方法有任何建议,我很乐意听到它们!
----开始编辑2012-04-26 13:28 CST ----
谢谢大家的答案!
似乎可能有理由修改此数据库的架构,这很好。
将表组合到单个表中并不是一个可行的选项,因为每个类型的列数不超过30列(不幸的是,修改这些列不是一个选项)。这可能会导致每行中没有使用大部分列,这似乎是一个坏主意。
添加第4张表,如John Sikora和其他人提到的,可能是一个选项,但我想首先验证这一点。
将架构修改为:
CREATE TABLE t_AllItems(
[id] [bigint] IDENTITY(1,1) NOT NULL,
[itemType] [int] NOT NULL,
[AppName] [nvarchar](20) NOT NULL,
[ItemName] [nvarchar](32) NOT NULL,
CONSTRAINT [pk_t_AllItems] PRIMARY KEY CLUSTERED ( [id] )
CONSTRAINT [uc_t_AllItems] UNIQUE([id], [AppName], [ItemName])
) ON [PRIMARY]
CREATE TABLE t_Analog(
[itemId] [bigint] NOT NULL,
[Value] [float] NOT NULL,
FOREIGN KEY (itemId) REFERENCES t_AllItems(id)
)
CREATE TABLE t_Discrete(
[itemId] [bigint] NOT NULL,
[Value] [bit] NOT NULL,
FOREIGN KEY (itemId) REFERENCES t_AllItems(id)
)
CREATE TABLE t_Message(
[itemId] [bigint] NOT NULL,
[Value] [nvarchar](256) NOT NULL,
FOREIGN KEY (itemId) REFERENCES t_AllItems(id)
)
我对这种方法只有一个问题。这是否会强制子表中的唯一性?
例如,是否存在“项目”,其中“id”9表格为t_Analog,其中“itemId”为9,“value”为9.3,同时t_Message的'itemId'9为'价值''foo“?
我可能不完全理解这种额外的表格方法,但我并不反对。
如果我错了,请纠正我。
答案 0 :(得分:12)
专门为您想要独特的值添加第4个表,然后使用一对多关系将这些表中的这些键链接到其他表中。 例如,您将拥有一个唯一的表,其中包含ID,AppName和ItemName以构成其3列。然后将此表链接到其他人。
如何做到这一点就是一个很好的例子 Create a one to many relationship using SQL Server
编辑:这就是我要做的,但考虑到您的服务器需求,您可以更改所需内容:
CREATE TABLE AllItems(
[id] [int] IDENTITY(1,1) NOT NULL,
[itemType] [int] NOT NULL,
[AppName] [nvarchar](20) NOT NULL,
[ItemName] [nvarchar](32) NOT NULL,
CONSTRAINT [pk_AllItems] PRIMARY KEY CLUSTERED ( [id] ASC )
) ON [PRIMARY]
CREATE TABLE Analog(
[itemId] [int] NOT NULL,
[Value] [float] NOT NULL
)
CREATE TABLE Discrete(
[itemId] [int] NOT NULL,
[Value] [bit] NOT NULL
)
CREATE TABLE Message(
[itemId] [bigint] NOT NULL,
[Value] [nvarchar](256) NOT NULL
)
ALTER TABLE [Analog] WITH CHECK
ADD CONSTRAINT [FK_Analog_AllItems] FOREIGN KEY([itemId])
REFERENCES [AllItems] ([id])
GO
ALTER TABLE [Analog] CHECK CONSTRAINT [FK_Analog_AllItems]
GO
ALTER TABLE [Discrete] WITH CHECK
ADD CONSTRAINT [FK_Discrete_AllItems] FOREIGN KEY([itemId])
REFERENCES [AllItems] ([id])
GO
ALTER TABLE [Discrete] CHECK CONSTRAINT [FK_Discrete_AllItems]
GO
ALTER TABLE [Message] WITH CHECK
ADD CONSTRAINT [FK_Message_AllItems] FOREIGN KEY([itemId])
REFERENCES [AllItems] ([id])
GO
ALTER TABLE [Message] CHECK CONSTRAINT [FK_Message_AllItems]
GO
从我可以告诉你的语法很好,我只是简单地改变它,因为我对它更熟悉,但要么应该工作。
答案 1 :(得分:9)
虽然您可能会或可能不想像其他答案那样改变您的架构,但indexed view可以应用您正在谈论的约束:
CREATE VIEW v_Analog_Discrete_Message_UK WITH SCHEMABINDING AS
SELECT a.AppName, a.ItemName
FROM dbo.t_Analog a, dbo.t_Discrete b, dbo.t_Message c, dbo.Tally t
WHERE (a.AppName = b.AppName and a.ItemName = b.ItemName)
OR (a.AppName = c.AppName and a.ItemName = c.ItemName)
OR (b.AppName = c.AppName and b.ItemName = c.ItemName)
AND t.N <= 2
GO
CREATE UNIQUE CLUSTERED INDEX IX_AppName_ItemName_UK
ON v_Analog_Discrete_Message_UK (AppName, ItemName)
GO
你需要一个"Tally" or numbers table或者必须动态生成一个,Celko风格:
-- Celko-style derived numbers table to 100k
select a.N + b.N * 10 + c.N * 100 + d.N * 1000 + e.N * 10000 + 1 as N
from (select 0 as N union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) a
, (select 0 as N union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) b
, (select 0 as N union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) c
, (select 0 as N union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) d
, (select 0 as N union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) e
order by N
答案 2 :(得分:1)
一种想法可能是将三个表组合在一起:
CREATE TABLE t_Generic(
[AppName] [nvarchar](20) NOT NULL,
[ItemName] [nvarchar](32) NOT NULL,
[Type] [nvarchar](32) NOT NULL,
[AnalogValue] [Float] NULL,
[DiscreteValue] [bit] NULL,
[MessageValue] [nvarchar](256) NULL,
CONSTRAINT [uc_t_Generic] UNIQUE(AppName, ItemName)
)
您的应用程序逻辑必须强制只填充一个值,并且您可以使用“类型”字段来跟踪该记录的类型。
答案 3 :(得分:1)
您还可以创建一个具有更多逻辑的约束并检查所有三个表。
请查看here,了解如何使用函数执行此操作。
答案 4 :(得分:0)
这会建议规范化/数据库设计问题,特别是你应该将appname存储在它自己的一个表中(作为唯一/密钥),然后是第二列,表示它链接到的ID,也许第3列表示类型。
EG:
AppName – PrimaryKey - unique
ID – Foreign Key of either Discrete, Analog or message
Type – SMALLINT representing Discrete, analog or message.
答案 5 :(得分:0)
我使用而不是插入和更新触发器来解决此问题,如下所示:
CREATE TRIGGER tI_Analog ON t_Analog
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON ;
IF EXISTS (SELECT 1 FROM inserted AS I INNER JOIN t_Analog AS T
ON T.AppName = I.AppName AND T.ItemName = I.ItemName
UNION ALL
SELECT 1 FROM inserted AS I INNER JOIN t_Discrete AS T
ON T.AppName = I.AppName AND T.ItemName = I.ItemName
UNION ALL
SELECT 1 FROM inserted AS I INNER JOIN t_Message AS T
ON T.AppName = I.AppName AND T.ItemName = I.ItemName
)
BEGIN
RAISERROR('Duplicate key', 16, 10) ;
END
ELSE
BEGIN
INSERT INTO t_Analog ( AppName, ItemName, Value )
SELECT AppName, ItemName, Value FROM inserted ;
END
END
GO
CREATE TRIGGER tU_Analog ON t_Analog
INSTEAD OF UPDATE
AS
BEGIN
SET NOCOUNT ON ;
IF EXISTS (SELECT TOP(1) 1
FROM (SELECT T.AppName, T.ItemName, COUNT(*) AS numRecs
FROM
(SELECT I.AppName, I.ItemName
FROM inserted AS I INNER JOIN t_Analog AS T
ON T.AppName = I.AppName AND T.ItemName = I.ItemName
UNION ALL
SELECT I.AppName, I.ItemName
FROM inserted AS I INNER JOIN t_Discrete AS T
ON T.AppName = I.AppName AND T.ItemName = I.ItemName
UNION ALL
SELECT I.AppName, I.ItemName
FROM inserted AS I INNER JOIN t_Message AS T
ON T.AppName = I.AppName AND T.ItemName = I.ItemName
) AS T
GROUP BY T.AppName, T.ItemName
) AS T
WHERE T.numRecs > 1
)
BEGIN
RAISERROR('Duplicate key', 16, 10) ;
END
ELSE
BEGIN
UPDATE T
SET AppName = I.AppName
, ItemName = I.ItemName
, Value = I.Value
FROM inserted AS I INNER JOIN t_Message AS T
ON T.AppName = I.AppName AND T.ItemName = I.ItemName
;
END
END
GO
使用而不是触发器的一个警告是涉及到身份字段时。此触发器可防止INSERT INTO命令的OUTPUT子句和@@ IDENTITY变量正常工作。