Подтвердить что ты не робот

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"?

Я не могу полностью понять этот дополнительный подход к таблице, но я не против.

Пожалуйста, поправьте меня, если я ошибаюсь.

4b9b3361

Ответ 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.