SQL Уникальное ограничение для нескольких таблиц
Я пытаюсь создать уникальное ограничение для нескольких таблиц. Я нашел ответы на подобные вопросы, но они не совсем отражают дух того, что я пытаюсь сделать.
В качестве примера у меня есть три таблицы: 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)
)
Моя цель - сделать AppName и ItemName уникальными во всех трех таблицах. Например, имя элемента Y в приложении X не может существовать как в аналоговых, так и в дискретных таблицах.
Обратите внимание, что этот пример надуман, фактические данные для каждого типа различны и достаточно велики, чтобы объединить таблицы и добавить столбец типа довольно уродливо.
Если у вас есть предложения по подходу к этому, я бы с удовольствием их услышал.
---- BEGIN EDIT 2012-04-26 13:28 CST ----
Спасибо всем за ваши ответы!
Кажется, что может быть причина для изменения схемы этой базы данных, и это нормально.
Объединение таблиц в одну таблицу на самом деле не является жизнеспособным вариантом, так как существует порядка 30 столбцов для каждого типа, которые не соответствуют (изменение этих столбцов, к сожалению, не является вариантом). Это может привести к тому, что в каждой строке не будут использоваться большие секции столбцов, которые кажутся плохими.
Добавление четвертой таблицы, например, 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)
)
У меня есть только один вопрос относительно этого подхода. Обеспечивает ли это уникальность для всех подкатегорий?
Например, не может существовать "Item", который имеет "id" 9 с таблицами t_Analog, имеющих "itemId" из 9 с "значением" 9.3, и в то же время t_Message имеет "itemId" 9 с ' Значение "foo"?
Я не могу полностью понять этот дополнительный подход к таблице, но я не против.
Пожалуйста, поправьте меня, если я ошибаюсь.
Ответы
Ответ 1
Добавьте четвертую таблицу специально для этих значений, которые вы хотите быть уникальными, а затем привяжите эти ключи из этой таблицы к другим, используя отношения от одного до многих.
Например, у вас будет уникальная таблица с идентификатором, AppName и ItemName для создания трех столбцов. Затем привяжите эту таблицу к остальным.
Как сделать это здесь - хороший пример
Создайте отношения "один ко многим" с помощью SQL Server
EDIT: Это то, что я сделал бы, но учитывая потребности вашего сервера, вы можете изменить то, что необходимо:
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
Из того, что я могу сказать, ваш синтаксис в порядке, я просто изменил его таким образом просто потому, что я более знаком с ним, но и должен работать.
Ответ 2
Пока вы можете или не хотите изменять свою схему, как говорят другие ответы, индексированный вид может применить ограничение, которое вы говорите о:
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" или таблица чисел или придется иначе генерировать "на лету", Celko-style:
-- 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
Ответ 3
Вы также можете создать ограничение, которое имеет немного больше логики и проверит все три таблицы.
Посмотрите здесь на пример того, как это сделать с помощью функции.
Ответ 4
Можно подумать о том, чтобы объединить три таблицы:
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)
)
В вашей логике приложения должно быть указано, что было заполнено только одно значение, и вы можете использовать поле "Тип", чтобы отслеживать, какой тип записи является.
Ответ 5
Это предполагает проблему с нормализацией/конструкцией базы данных, в частности, вы должны иметь имя приложения, сохраненное в одной таблице самостоятельно (как уникальное/ключевое), а затем второй столбец, обозначающий идентификатор того, с чем он связан, и, возможно, 3-й столбец с указанием типа.
EG:
AppName – PrimaryKey - unique
ID – Foreign Key of either Discrete, Analog or message
Type – SMALLINT representing Discrete, analog or message.
Ответ 6
Я использовал вместо триггеров вставки и обновления, чтобы решить эту проблему следующим образом:
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
Одно предупреждение при использовании вместо триггеров - это когда есть поле идентификатора. Этот триггер предотвращает правильную работу предложения OUTPUT команды INSERT INTO и переменной @@IDENTITY.