既存の違反を無視する一意の制約を追加できますか?


40

現在、列に重複した値があるテーブルがあります。

これらの誤った重複を削除することはできませんが、一意でない値が追加されないようにしたいと思います。

UNIQUE既存のコンプライアンスをチェックしないを作成できますか?

使用してみましNOCHECKたが、失敗しました。

この場合、ライセンス情報を「CompanyName」に関連付けるテーブルがあります

編集:同じ「CompanyName」を持つ複数の行を持つことは悪いデータですが、現時点ではそれらの重複を削除または更新することはできません。1つのアプローチはINSERT、重複に対して失敗するストアドプロシージャをsに使用させることです... SQLが独自に一意性をチェックすることが可能であれば、それが望ましいでしょう。

このデータは会社名で照会されます。少数の既存の重複について、これは複数の行が返されて表示されることを意味します...これは間違っていますが、ユースケースでは許容できます。目標は、将来それを防ぐことです。コメントから、ストアドプロシージャでこのロジックを実行する必要があるようです。


テーブルを変更できますか(列をもう1つ追加します)?
ypercubeᵀᴹ

残念ながら@ypercubeは違います。
マシュー

回答:


33

答えは「はい」です。これは、フィルター選択されたインデックスを使用して実行できます(ドキュメントについては、こちらをご覧ください)。

たとえば、次のことができます。

create unique index t_col on t(col) where id > 1000;

これにより、古い行ではなく、新しい行にのみ一意のインデックスが作成されます。この特定の定式化により、既存の値と重複することができます。

少数の重複がある場合、次のようなことができます。

create unique index t_col on t(col) where id not in (<list of ids for duplicate values here>);

2
それが良いかどうかは、「古い」既存のアイテムが同じ値を持つ新しいアイテムの作成を妨げるかどうかに依存します。
supercat

1
@supercat。。。既存の重複値を除くすべてのインデックスを作成するための代替の定式化を行いました。
ゴードンリノフ

1
後者が機能するためには、重複した個別のキー値ごとに1つのidをリストから削除する必要があります。また、リストから意図的に省略された項目がテーブルから削除された場合も確認する必要があります、等しいキーを持つアイテムはリストから削除されます。
supercat

@supercat。。。同意する。トリガーでインデックスを再作成できないため、更新と削除のインデックスの一貫性を保つことはさらに困難です。いずれにせよ、私はOPから、データ(または少なくとも重複)が頻繁に変更されないという印象を受けました。
ゴードンリノフ

IDのリストではなく、値のリストを除外しないのはなぜですか?そして、あなたは除外IDのリストから重複した値ごとに1つのIDを除外する必要はありません
JMD合体

23

はい、できます。

重複したテーブルを次に示します。

CREATE TABLE dbo.Party
  (
    ID INT NOT NULL
           IDENTITY ,
    CONSTRAINT PK_Party PRIMARY KEY ( ID ) ,
    Name VARCHAR(30) NOT NULL
  ) ;
GO

INSERT  INTO dbo.Party
        ( Name )
VALUES  ( 'Frodo Baggins' ),
        ( 'Luke Skywalker' ),
        ( 'Luke Skywalker' ),
        ( 'Harry Potter' ) ;
GO

既存のものを無視し、新しい重複を追加できないようにします。

-- Add a new column to mark grandfathered duplicates.
ALTER TABLE dbo.Party ADD IgnoreThisDuplicate INT NULL ;
GO

-- The *first* instance will be left NULL.
-- *Secondary* instances will be set to their ID (a unique value).
UPDATE  dbo.Party
SET     IgnoreThisDuplicate = ID
FROM    dbo.Party AS my
WHERE   EXISTS ( SELECT *
                 FROM   dbo.Party AS other
                 WHERE  other.Name = my.Name
                        AND other.ID < my.ID ) ;
GO

-- This constraint is not strictly necessary.
-- It prevents granting further exemptions beyond the ones we made above.
ALTER TABLE dbo.Party WITH NOCHECK
ADD CONSTRAINT CHK_Party_NoNewExemptions 
CHECK(IgnoreThisDuplicate IS NULL);
GO

SELECT * FROM dbo.Party;
GO

-- **THIS** is our pseudo-unique constraint.
-- It works because the grandfathered duplicates have a unique value (== their ID).
-- Non-grandfathered records just have NULL, which is not unique.
CREATE UNIQUE INDEX UNQ_Party_UniqueNewNames ON dbo.Party(Name, IgnoreThisDuplicate);
GO

このソリューションをテストしてみましょう。

-- cannot add a name that exists
INSERT  INTO dbo.Party
        ( Name )
VALUES  ( 'Frodo Baggins' );

Cannot insert duplicate key row in object 'dbo.Party' with unique index 'UNQ_Party_UniqueNewNames'.

-- cannot add a name that exists and has an ignored duplicate
INSERT  INTO dbo.Party
        ( Name )
VALUES  ( 'Luke Skywalker' );

Cannot insert duplicate key row in object 'dbo.Party' with unique index 'UNQ_Party_UniqueNewNames'.


-- can add a new name 
INSERT  INTO dbo.Party
        ( Name )
VALUES  ( 'Hamlet' );

-- but only once
INSERT  INTO dbo.Party
        ( Name )
VALUES  ( 'Hamlet' );

Cannot insert duplicate key row in object 'dbo.Party' with unique index 'UNQ_Party_UniqueNewNames'.

4
彼がテーブルに列を追加できないことを除いて。
アーロンバートランド

3
この回答が、一意の制約で非標準的な方法でNULL値を処理する方法を有用なものに変える方法が好きです。Cなトリック。
ypercubeᵀᴹ

@ypercubeᵀᴹ、ユニーク制約でのNULL処理に関する非標準とは何か説明できますか?それはあなたが期待していたものとどう違うのですか?ありがとう!
-Noach

1
SQL Serverの@NoachではUNIQUE、null許容列の制約により、最大で1つのNULL値のみが確保されます。SQL標準(および他のほとんどすべてのSQL DBMS)では、任意の数のNULL値を許可する必要があるとされています(つまり、制約はNULL値を無視する必要があります)。
ypercubeᵀᴹ

@ypercubeᵀᴹしたがって、これを別のDBMSに実装するには、NULLではなくDEFAULT 0を使用するだけです。正しい?
-Noach

16

フィルター処理された一意のインデックスは素晴らしいアイデアですが、WHERE identity_column > <current value>条件を使用するか、またはWHERE identity_column NOT IN (<list of ids for duplicate values here>)

最初のアプローチでは、今後も既存の(現在の)データの複製である重複データを挿入できます。たとえば、現在1行しかない場合でもCompanyName = 'Software Inc.'、行は同じ会社名でもう1行挿入することを禁止しません。2回試行した場合にのみ禁止されます。

2番目のアプローチでは改善がありますが、上記は機能しません(これは良いことです)。しかし、さらに多くの重複または既存の重複を挿入できます。たとえば、で(2つ以上の)行があるCompanyName = 'DoubleData Co.'場合、インデックスは同じ会社名の行をもう1つ挿入することを禁止しません。2回試行した場合にのみ禁止されます。

(更新)重複する名前ごとに、1つのIDを除外リストに含めない場合、これを修正できます。上記の例のように、重複CompanyName = DoubleData Co.とIDを持つ4つの行がある場合、4,6,8,9除外リストにはこれらのIDが3つしかありません。

2番目のアプローチでは、SQL-Server がフィルター選択されたインデックスの一部でNOT IN演算子をサポートしていないように見えるため、面倒な状態(最初の場所にある重複の数に依存する面倒さ)も欠点ですWHERESQL-Fiddleを参照してください。の代わりにWHERE (CompanyID NOT IN (3,7,4,6,8,9))WHERE (CompanyID <> 3 AND CompanyID <> 7 AND CompanyID <> 4 AND CompanyID <> 6 AND CompanyID <> 8 AND CompanyID <> 9)何百もの重複した名前がある場合、そのような条件に効率的な影響があるかどうかわからないようなものを持っている必要があります。


別の解決策(@Alex Kuznetsovに類似)は、別の列を追加し、ランク番号を入力して、この列を含む一意のインデックスを追加します。

ALTER TABLE Company
  ADD Rn TINYINT DEFAULT 1;

UPDATE x
SET Rn = Rnk
FROM
  ( SELECT 
      CompanyID,
      Rn,
      Rnk = ROW_NUMBER() OVER (PARTITION BY CompanyName 
                               ORDER BY CompanyID)
    FROM Company 
  ) x ;

CREATE UNIQUE INDEX CompanyName_UQ 
  ON Company (CompanyName, Rn) ; 

次に、DEFAULT 1プロパティと一意のインデックスのために、重複した名前の行の挿入は失敗します。これはまだ100%絶対確実なものではありません(アレックスのものはそうです)。RnINSERTステートメントで明示的に設定されている場合、またはRn値が悪意を持って更新された場合、複製は引き続きスリップします。

SQL-フィドル-2


-2

別の方法は、テーブルに値が既に存在するかどうかをチェックするスカラー関数を作成し、チェック制約からその関数を呼び出すことです。

これはパフォーマンスに恐ろしいことをします。



アーロンが指摘した問題に加えて、このチェック制約をどのように追加することができるかについては答えが説明していないため、既存の重複を無視します。
ypercubeᵀᴹ

-2

私は同じものを探しています-信頼できない一意のインデックスを作成して、既存の不良データを無視しますが、新しいレコードは既存のものの複製にはできません。

このスレッドを読んでいる間、より良い解決策は、親テーブルに対して重複をチェックする[挿入]トリガーを書くことであり、それらのテーブル間に重複がある場合は、ROLLBACK TRANです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.