唯一(多列)并在一列中为null

时间:2014-09-15 09:27:12

标签: mysql sql database unique-constraint

我有简单的类别表。类别可以包含父类别(par_cat列),如果它是主类别,则为null,并且具有相同的父类别,不应该是具有相同名称或网址的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_cat设置为null且名称和网址相同的数据库2插入数据库中,则可以将这两个类别插入到数据库中而不会出现问题(并且他们不应该这样做)。但是,如果我选择其他类别par_cat(例如1假设存在id为1的类别),则只会插入第一条记录(以及所需的行为)。

问题 - 如何处理此案件?我读到了:

  

UNIQUE索引创建一个约束,以便索引中的所有值   必须是截然不同的如果您尝试使用a添加新行,则会发生错误   与现有行匹配的键值。此约束不适用   除了BDB存储引擎之外的NULL值。对于其他引擎,a   UNIQUE索引允许可以包含的列的多个NULL值   空值。如果为UNIQUE索引中的列指定前缀值,   列值在前缀中必须是唯一的。

但是,如果我在多个列上具有唯一性,我预计情况并非如此(只有par_cat可以为空,nameurl不能为空)。由于par_cat引用了同一个表中的id,但某些类别没有父类别,因此应该允许null个值。

2 个答案:

答案 0 :(得分:27)

这适用于SQL标准的定义。 NULL表示未知。如果您有两个par_cat = NULL和name =' X'的记录,那么这两个NULL不会被视为保持相同的值。因此,他们不会违反唯一的密钥约束。 (好吧,有人可能会认为NULL仍然可能意味着相同的值,但应用此规则将使得使用唯一索引和可空字段几乎是不可能的,因为NULL也可能意味着1,2或其他其他价值。所以他们很好地定义了它,就像我认为的那样。)

由于MySQL不支持您可以在ISNULL(par_cat,-1), name上创建索引的功能索引,因此您唯一的选择是使par_cat为0或-1的NOT NULL列或者#34;没有父级&#34 ;,如果你想让你的约束起作用。

答案 1 :(得分:0)

我看到这是在2014年提出的。 但是,通常是从MySQL请求的:例如https://bugs.mysql.com/bug.php?id=8173https://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); 

generate_par_cat是一个虚拟的生成列,因此它没有存储空间。当用户插入(或更新)时,唯一索引会导致动态生成generate_par_cat的值,这是一个非常快速的操作。