【问题标题】:Unique (multiple columns) and null in one column唯一(多列)并且在一列中为空
【发布时间】:2014-11-08 18:53:36
【问题描述】:

我有简单的类别表。类别可以有父类别(par_cat 列),如果是主类别并且父类别相同,则不应有 2 个或多个具有相同名称或 url 的类别。

此表的代码:

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 且名称和url 相同的2 个类别插入数据库,那么这2 个类别可以毫无问题地插入数据库(它们不应该)。但是,如果我为其他类别选择 par_cat(例如 1 假设存在 id 1 的类别),则只会插入第一条记录(这是所需的行为)。

问题 - 如何处理这种情况?我读到:

一个 UNIQUE 索引创建一个约束,使得索引中的所有值 必须是不同的。如果您尝试添加一个带有 与现有行匹配的键值。此约束不适用 除 BDB 存储引擎外,为 NULL 值。对于其他发动机,一个 UNIQUE 索引允许包含多个 NULL 值的列 空值。如果为 UNIQUE 索引中的列指定前缀值, 列值在前缀中必须是唯一的。

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

【问题讨论】:

  • This answer 可以解释您遇到的问题。建议您将 par_cat 更改为不为 null,为 root 设置一个 minimun id,例如 0。并从 1 自动增加类别 ID。
  • @JaugarChang 但是我也有 par_cat 的外键约束,所以我不能使用值 0 因为没有 id 为 0 的记录
  • 或者你可以添加一个 id 为 0 的根记录。但我认为没有必要使用外键。您最好在应用程序端而不是数据库端强制执行引用完整性。

标签: mysql sql database unique-constraint


【解决方案1】:

以防万一你来自 Laravel...

这是 Laravel 的 Virtual Column 迁移版本,当其中一个列的值为 NULL 时,解决 UNIQUE 问题

$table->integer('generated_par_cat')->virtualAs('ifNull(par_cat, 0)');

$table->unique(['name', 'generated_par_cat'], 'name_par_cat_unique');

【讨论】:

    【解决方案2】:

    我看到这是在 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); 
    

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

    【讨论】:

    • 谢谢!感谢 MySQL 5.7 的"Generated Columns" feature,可以确认这是否有效。我们遇到了唯一索引日期时间(软删除:deleted_at 列)的 NULL 问题,因此我们没有将 NULL 归零,而是将 `stringified_deleted_at` char(19) GENERATED ALWAYS AS (CONVERT(IFNULL(deleted_at, ""),char)) STORED 字符串化
    • 我建议原始发帖人将此评论标记为已接受的答案。它使用原生 MySQL 5.7+ 功能解决了这个问题,而无需求助于虚拟记录等。
    【解决方案3】:

    这按 SQL 标准的定义工作。 NULL 表示未知。如果你有两条 par_cat = NULL 和 name = 'X' 的记录,那么这两个 NULL 不被认为持有相同的值。因此它们不会违反唯一键约束。 (好吧,有人可能会争辩说,NULL 仍然可能表示相同的值,但应用此规则将使使用唯一索引和可空字段几乎不可能,因为 NULL 也可能表示 1、2 或其他其他值。所以他们很好地定义了它,就像我认为的那样。)

    由于 MySQL 不支持您可以在 ISNULL(par_cat,-1), name 上拥有索引的功能索引,因此您唯一的选择是将 par_cat 设为 NOT NULL 列,其中包含 0 或 -1 或“无父级”的任何值,如果您想要您的约束上班。

    【讨论】:

    • 好的,我明白了。但是,如果我不将其设为 NULL,我将无法插入 par_cat 值 0,因为外键约束 FOREIGN KEY (par_cat) REFERENCES categories` (id);. So does it mean that I have to choose between 2 constraints - either use foreign key (with null) and don't use unique or use unique but remove foreign key on par_cat` 因为这两个约束不能一起工作?
    • 或者将id为0的虚拟记录放入分类中。
    • 好的,但是我需要先把它放在创建外键之前,否则它不能插入到数据库中,对吧?在这种情况下,通常的做法是什么?放置这样的 0 记录(如您所建议的)还是不使用外键或唯一键?使用0 record 以防万一我想从数据库中获取所有内容,我需要始终从结果中删除那些0 record
    • 你是对的,没有完美的解决方案。我会将该虚拟记录放在表中,因此数据库完整性由外键约束保护。您是对的,尽管每次都必须记住要排除类别 0 可能很烦人。您可以在该表上编写一个视图,以便为方便起见排除记录。至于惯例:我不知道。我的回答中提到的功能索引会很棒。如果它在 MySQL 中可用,那是我会选择的道路。
    猜你喜欢
    • 1970-01-01
    • 2012-08-11
    • 2013-12-07
    • 2021-11-14
    • 2011-11-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多