26

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

例として、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)
)

私の目標は、AppName と ItemName を 3 つのテーブルすべてで一意にすることです。たとえば、アプリケーション X のアイテム名 Y は、アナログ テーブルとディスクリート テーブルの両方に存在することはできません。

この例は不自然であることに注意してください。各 Type の実際のデータは異なり、テーブルを結合して Type 列を追加するには十分な大きさです。

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

---- 編集開始 2012-04-26 13:28 CST ----

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

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

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

John Sikora や他の人が言及しているように、4 番目のテーブルを追加することはオプションかもしれませんが、最初にこれを確認したいと思います。

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

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

このアプローチに関して 1 つだけ質問があります。これにより、サブテーブル全体で一意性が強制されますか?

たとえば、テーブル t_Analog の 'itemId' が 9 で 'value' が 9.3 の 'id' 9 を持つ 'Item' が存在し、同時に t_Message の 'itemId' 9 と 'Value' が「フ」?

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

これについて間違っている場合は修正してください。

4

6 に答える 6

14

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

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

編集:これは私が行うことですが、サーバーのニーズを考慮して、必要なものを変更できます:

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

あなたの構文は問題ないと私が言えることから、私は単にこの方法に慣れているという理由だけでこの方法に変更しましたが、どちらも機能するはずです。

于 2012-04-26T17:00:00.263 に答える
11

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

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 スタイルでオンザフライで生成する必要があります。

-- 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
于 2012-04-26T17:57:42.327 に答える
1

1つの考えは、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つの値のみが入力されるように強制する必要があり、[タイプ]フィールドを使用して、そのレコードがどのタイプであるかを追跡できます。

于 2012-04-26T17:12:10.383 に答える
1

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

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

于 2012-04-26T18:14:17.620 に答える
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

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

于 2012-06-29T18:35:49.397 に答える
0

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

例えば:

AppName – PrimaryKey - unique
ID – Foreign Key of either Discrete, Analog or message
Type – SMALLINT representing Discrete, analog or message.
于 2012-04-26T17:01:19.013 に答える