web-dev-qa-db-ja.com

Sqlite NULLと一意ですか?

UNIQUE制約のある列にNULL値を含めることができることに気付きました:UNIQUE(col)

特定の状況で問題が発生しますか?

24
katie

以下は複数のnull値に対処しますが、データベース/ SQLの移植性の可能性を除いて、そのような設計に関連する「問題」には対処しません 、おそらくnotは答えと見なされるべきではなく、単に参照のためにここに残されています。


これは実際にはSQLiteFAQでカバーされています。これは設計上の選択です-SQLite(SQL Serverとは異なり)は、複数のNULL値がインデックスの一意性にカウントされないことを選択しました。

SQL標準では、制約内の1つ以上の列がNULLであっても、UNIQUE制約を適用する必要がありますが、SQLiteはこれを行いません。これはバグではありませんか?

おそらく、SQL92の次のステートメントを参照しています。

  • テーブル内の2つの行の一意の列に同じ非NULL値がない場合にのみ、一意の制約が満たされます。

そのステートメントはあいまいであり、少なくとも2つの可能な解釈があります。

  1. テーブル内の2つの行に同じ値がなく、一意の列にnull以外の値がある場合にのみ、一意性制約が満たされます。

  2. 一意性制約は、テーブル内の2つの行が、nullではない一意性列のサブセットに同じ値を持たない場合にのみ満たされます。

SQLiteは、PostgreSQL、MySQL、Oracle、Firebirdと同様に、解釈(1)に従います。 InformixとMicrosoftSQL Serverが解釈(2)を使用しているのは事実ですが、SQLite開発者は、解釈(1)が要件の最も自然な読み取りであり、他のSQLデータベースエンジンや他のほとんどのSQLデータベースエンジンとの互換性を最大化したいと考えています。データベースエンジンも(1)に対応しているため、SQLiteはそれを実行します。

NULL処理の比較 を参照してください。

35
user2864740

NULL列を無視した場合に2つの行が同じであるときに一意のインデックスでエラーをスローしたい場合(およびSatyamの回答からのトリガーを使用したくない場合)、次のようにすることができます

CREATE TABLE `test` (
    `Field1`    INTEGER,
    `Field2`    INTEGER
);
CREATE UNIQUE INDEX `ix` ON `test` (
    `Field1`,
    `Field2`
);
INSERT INTO `test`(`Field1`,`Field2`) VALUES (1,NULL);
INSERT INTO `test`(`Field1`,`Field2`) VALUES (1,NULL); -- This shouldn't be allowed

DROP INDEX IF EXISTS `ix`;
CREATE UNIQUE INDEX `ix2` ON `test` (
    `Field1`,
    ifnull(`Field2`, 0)  --use this instead
); --will fail
3
JanHudecek

テーブルに2つのトリガーを作成して、挿入または更新操作の前に列がnullの行が存在するかどうかを確認できます。存在する場合は、例外が発生します。

CREATE TRIGGER UniqueColumnCheckNullInsert
        BEFORE INSERT
        ON 'Tablename'
        WHEN NEW.'column_name' IS NULL
        BEGIN
        SELECT CASE WHEN((
            SELECT 1
            FROM 'Tablename'
            WHERE 'column_name' IS NULL
            )
            NOTNULL) THEN RAISE(ABORT, "error row exists") END;
    END;

CREATE TRIGGER UniqueColumnCheckNullUpdate
        BEFORE UPDATE
        ON 'Tablename'
        WHEN NEW.'column_name' IS NULL
        BEGIN
        SELECT CASE WHEN((
            SELECT 1
            FROM 'Tablename'
            WHERE 'column_name' IS NULL
            )
            NOTNULL) THEN RAISE(ABORT, "error row exists") END;
    END;
3
Satyam Raikar