web-dev-qa-db-ja.com

一意(複数の列)および1つの列でnull

簡単なカテゴリテーブルがあります。カテゴリは親カテゴリ(par_cat列)を持つことができます。メインカテゴリであり、同じ親カテゴリを持つ場合、同じ名前またはURLの2つ以上のカテゴリがあってはなりません。

このテーブルのコード:

CREATE TABLE IF NOT EXISTS `categories` (
`id` int(10) unsigned NOT NULL,
  `par_cat` int(10) unsigned DEFAULT NULL,
  `lang` varchar(2) COLLATE utf8_unicode_ci NOT NULL DEFAULT 'pl',
  `name` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
  `url` varchar(120) COLLATE utf8_unicode_ci NOT NULL,
  `active` tinyint(3) unsigned NOT NULL DEFAULT '1',
  `accepted` tinyint(3) unsigned NOT NULL DEFAULT '1',
  `priority` int(10) unsigned NOT NULL DEFAULT '1000',
  `entries` int(10) unsigned NOT NULL DEFAULT '0',
  `created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
  `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00'
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=3 ;



ALTER TABLE `categories`
  ADD PRIMARY KEY (`id`), 
  ADD UNIQUE KEY `categories_name_par_cat_unique` (`name`,`par_cat`), 
  ADD UNIQUE KEY `categories_url_par_cat_unique` (`url`,`par_cat`), 
  ADD KEY `categories_par_cat_foreign` (`par_cat`);


ALTER TABLE `categories`
  MODIFY `id` int(10) unsigned NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=3;

ALTER TABLE `categories`ADD CONSTRAINT `categories_par_cat_foreign` 
  FOREIGN KEY (`par_cat`) REFERENCES `categories` (`id`);

問題は、一意のキーを持っている場合でも機能しないことです。 par_catnullに設定され、同じ名前とURLを持つ2つのカテゴリをデータベースに挿入しようとすると、それら2つのカテゴリは問題なくデータベースに挿入できます(そうすべきではありません)。ただし、これらのカテゴリに対して他のpar_catを選択した場合(たとえば、IDが1のカテゴリが存在すると仮定して1)、最初のレコードのみが挿入されます(これが望ましい動作です)。

質問-このケースをどのように処理しますか?私はそれを読みました:

UNIQUEインデックスは、インデックス内のすべての値が異なる必要があるような制約を作成します。既存の行と一致するキー値を持つ新しい行を追加しようとすると、エラーが発生します。この制約は、BDBストレージエンジンを除き、NULL値には適用されません。他のエンジンの場合、UNIQUEインデックスは、NULLを含むことができる列に対して複数のNULL値を許可します。 UNIQUEインデックスの列にプレフィックス値を指定する場合、列値はプレフィックス内で一意である必要があります。

ただし、複数の列に一意の場合はそうではないと予想しました(par_catのみnullにすることができ、nameおよびurlをnullにすることはできません)。 par_catは同じテーブルのidを参照しますが、一部のカテゴリには親カテゴリがないため、nullの値を許可する必要があります。

20

これは、SQL標準の定義に従って機能します。 NULLは不明を意味します。 par_cat = NULLおよびname = 'X'の2つのレコードがある場合、2つのNULLは同じ値を保持しているとは見なされません。したがって、それらは一意のキー制約に違反しません。 (まあ、NULLはまだ同じだと主張できますmightは同じ値を意味しますが、このルールを適用すると、一意のインデックスとnull可能なフィールドを操作することがほとんど不可能になります。NULLも1、2など他の価値なので、彼らは私の意見のようにうまく定義しました。)

MySQLはISNULL(par_cat,-1), nameにインデックスを設定できる関数インデックスをサポートしていないため、必要な場合は、par_catを0または-1などの「親なし」のNOT NULL列にすることが唯一のオプションです。動作するように制約。

28

これは2014年に尋ねられたようです。ただし、MySQLから頻繁に要求されます: https://bugs.mysql.com/bug.php?id=817 および https:// bugs.mysql.com/bug.php?id=17825 たとえば。人々はクリックすることができ、MySQLから注目を集めようとします。

MySQL 5.7以降、次の回避策を使用できるようになりました。

ALTER TABLE categories 
ADD generated_par_cat INT UNSIGNED AS (ifNull(par_cat, 0)) NOT NULL,
ADD UNIQUE INDEX categories_name_generated_par_cat (name, generated_par_cat), 
ADD UNIQUE INDEX categories_url_generated_par_cat (url, generated_par_cat); 

Generated_pa​​r_catは仮想生成列であるため、ストレージスペースはありません。ユーザーが挿入(または更新)すると、一意のインデックスにより、generated_pa​​r_catの値がその場で生成されます。これは非常に迅速な操作です。

2
Yoseph