web-dev-qa-db-ja.com

複数のテーブルにわたるSQL一意制約

複数のテーブルにわたって一意の制約を作成しようとしています。ここで同様の質問の答えを見つけましたが、それらは私がやろうとしていることの精神を完全に捉えていません。

例として、t_Analog、t_Discrete、t_Messageの3つのテーブルがあります。

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は、アナログテーブルと個別テーブルの両方に存在することはできません。

この例は不自然であることに注意してください。各タイプの実際のデータは異なっており、テーブルを組み合わせてタイプ列を追加するのにかなり見苦しいほど大きくなります。

これに対するアプローチについて何か提案があれば、ぜひ聞かせてください!

---- BEGIN EDIT 2012-04-26 13:28 CST ----

ご回答ありがとうございます!

このデータベースのスキーマを変更する原因があるようです、それは問題ありません。

テーブルを1つのテーブルに結合することは、一致しないタイプごとに約30の列があるため、実際に実行可能なオプションではありません(これらの列の変更は、残念ながらオプションではありません)。これにより、列の大きなセクションが各行で使用されなくなる可能性がありますが、これは悪い考えのようです。

John Sikoraや他の人が言及しているように、4番目のテーブルを追加することも1つの選択肢かもしれませんが、これを最初に確認したいと思います。

スキーマを次のように変更します。

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で、「itemId」が9、「value」が9.3である「item」が存在し、同時にt_Messageが「itemId」が「Value」が9の「Item」が存在しなかったとします。 「フー」?

私はこの余分なテーブルのアプローチを完全には理解していないかもしれませんが、それには反対しません。

私がこれについて間違っているなら、私を訂正してください。

22
CoryC

一意にするこれらの値専用の4番目のテーブルを追加し、1対多の関係を使用して、これらのキーをこのテーブルから他のキーにリンクします。たとえば、3つの列を構成するID、AppName、ItemNameを持つ一意のテーブルがあります。次に、このテーブルを他のテーブルにリンクさせます。

これを行う方法については、ここに良い例があります SQL Serverを使用して1対多の関係を作成します

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

私はあなたの構文が良いと言えることから、私はそれをこの方法に変更しました。なぜなら、私はそれに慣れているからですが、どちらもうまくいくはずです。

12
John Sykor

他の答えが言うようにスキーマを変更したい場合とそうでない場合がありますが、 インデックス付きビュー はあなたが話している制約を適用できます:

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 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
10
Tim Lehner

3つのテーブルを組み合わせることが考えられます。

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

アプリケーションロジックは、1つの値のみが入力されていることを強制する必要があり、Typeフィールドを使用して、そのレコードのタイプを追跡できます。

1
Victor Bruno

もう少しロジックがあり、3つのテーブルすべてをチェックする制約を作成することもできます。

関数を使用してこれを行う方法の例については、 ここ を参照してください。

1
zimdanen

次のように、この問題を解決するために、挿入および更新トリガーの代わりに使用しました。

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

トリガーの代わりにを使用した場合の警告の1つは、IDフィールドが含まれている場合です。このトリガーにより、INSERT INTOコマンドのOUTPUT句と@@ IDENTITY変数が正しく機能しなくなります。

0

これは、正規化/データベース設計の問題を示唆します。具体的には、1つのテーブルにアプリ名を(一意の/キーとして)独自に格納し、リンクされているもののIDを示す2番目の列、およびおそらく3番目の列を保持する必要がありますタイプを示します。

例えば:

AppName – PrimaryKey - unique
ID – Foreign Key of either Discrete, Analog or message
Type – SMALLINT representing Discrete, analog or message.
0
HeavenCore