SQL Unique constraint across multiple tables

前端 未结 6 1377
执念已碎
执念已碎 2020-12-01 18:09

I am trying to create a unique constraint across multiple tables. I have found similar questions answered here but they don\'t quite capture the spirit of what I am trying

相关标签:
6条回答
  • 2020-12-01 18:30

    This would suggest a normalisation / database design issue, specifically you should have the appname stored in one table on it’s own (as a unique / key whatever) then a 2nd column denoting the ID of what it is linked to, and perhaps a 3rd column indicating type.

    EG:

    AppName – PrimaryKey - unique
    ID – Foreign Key of either Discrete, Analog or message
    Type – SMALLINT representing Discrete, analog or message.
    
    0 讨论(0)
  • 2020-12-01 18:37

    While you may or may not want to alter your schema like other answers say, an indexed view can apply the constraint that you're talking about:

    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
    

    You will need a "Tally" or numbers table or have to otherwise generate one on the fly, 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
    
    0 讨论(0)
  • 2020-12-01 18:40

    Add a 4th table specifically for these values you want to be unique then link these keys from this table into the others using a one to many relationship. For example you will have the unique table with an ID, AppName and ItemName to make up its 3 columns. Then have this table link to the others.

    For how to do this here is a good example Create a one to many relationship using SQL Server

    EDIT: This is what I would do but considering your server needs you can change what is needed:

    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
    

    From what I can tell your syntax is fine, I simply changed it to this way simply because I am more familiar with it but either should work.

    0 讨论(0)
  • 2020-12-01 18:41

    You could also create a constraint that has a bit more logic and checks all three tables.

    Take a look here for an example of how to do this using a function.

    0 讨论(0)
  • 2020-12-01 18:47

    I used instead of insert and update triggers to resolve this issue like the following:

    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
    

    One warning with using instead of triggers is when there is an identity field involved. This trigger prevents the OUTPUT clause of the INSERT INTO command and the @@IDENTITY variable from working properly.

    0 讨论(0)
  • 2020-12-01 18:55

    One thought might be to combine the three tables:

    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)
    )
    

    Your application logic would have to enforce that only one value was populated, and you could use a Type field to keep track of what type that record is.

    0 讨论(0)
提交回复
热议问题