【问题标题】:Preferred way to create composite unique index with NULL values使用 NULL 值创建复合唯一索引的首选方法
【发布时间】:2016-07-05 18:18:47
【问题描述】:

我的数据库中有以下值:

我想在tv_series_id+name+tv_season_number+tv_episode_number 上创建一个唯一键。但是,对于我使用常规的目的,上述内容并不适用:

ALTER TABLE main_itemmaster ADD UNIQUE KEY (tv_series_id, tv_season_number, tv_episode_number, name)

因为空值。在数据库级别(而不是查询端)解决这个问题的方法是什么?

澄清一下,以上内容是不允许的,因为“97736-Season 4-4-NULL”重复了 10 次。作为最后的手段,我考虑实施的解决方案是存储一个额外的字符串列以实现唯一性,因此上述内容为“97736-Season 4-4-”。

【问题讨论】:

  • 我很困惑。 MySQL 允许在唯一索引 (dev.mysql.com/doc/refman/5.7/en/create-index.html) 中多次出现 NULL 值。所以,你的方法应该有效。
  • @GordonLinoff 更新后的最后一句话是否有助于澄清?
  • 你必须允许空值吗?您不能为 tv_episode_number 设置一个默认值(例如 0)吗?
  • 一个 ON INSERT 触发器,用于创建合成键,然后您可以将其包含在合成键中。
  • @evsheino 是的,会有空值,例如,电视季或电影没有剧集编号。将其存储为 0 可能会解决这个问题,但会搞砸很多其他事情。

标签: mysql sql indexing


【解决方案1】:

根据要求,我的建议(未经测试):

ALTER TABLE main_itemmaster ADD COLUMN uniquer BIGINT NOT NULL;
DELIMITER $$
CREATE TRIGGER add_uniquer BEFORE INSERT ON main_itemmaster
FOR EACH ROW
  BEGIN
    SET NEW.uniquer = (SELECT UUID_SHORT());
  END $$
DELIMITER ;
ALTER TABLE main_itemmaster ADD UNIQUE KEY (tv_series_id, tv_season_number, tv_episode_number, name, uniquer);

uniquer 背后的算法完全取决于您。我在这里选择了short UUID。您可能需要一个长 UUID。或 UUID might not be right for you。基于时间的可能会起作用。您可以基于插入行中值的 MD5。

【讨论】:

  • 与原始约束相比,这如何加强唯一性? (97736, 4, NULL, 'Season 4', generated_id1) 和 (97736, 4, NULL, 'Season 4', generated_id2) 都是唯一的。
  • 这个解决方案不会阻止UNIQUE 的任何实用程序吗?也就是说,由于额外的“唯一”列,它不会允许 '97736-Season 4-4-foo' 出现两次吗?
  • @RickJames 可能。 OP 排除了有关其他数据字段的语义信息,并且其他信息实际上可能会取消具有这些结果的查询的资格。虽然我的回答严格回答了这个问题,但我想知道具有不同自然键的不同方法是否更有意义。 IOW,我对可能是战略问题的问题给出了战术答案。
【解决方案2】:

使用https://stackoverflow.com/a/27053371/4321262中给出的答案

ALTER TABLE main_itemmaster ADD uniq int UNSIGNED NOT NULL DEFAULT 0;

ALTER TABLE main_itemmaster ADD UNIQUE KEY (tv_series_id, tv_season_number, name, uniq);

DELIMITER ;;
CREATE TRIGGER main_itemmaster_before_insert BEFORE INSERT ON main_itemmaster
FOR EACH ROW
BEGIN
    SET NEW.uniq = IFNULL(NEW.tv_episode_number, 0);
END;;

CREATE TRIGGER main_itemmaster_before_update BEFORE UPDATE ON main_itemmaster
FOR EACH ROW
BEGIN
    SET NEW.uniq = IFNULL(NEW.tv_episode_number, 0);
END;;
DELIMITER ;

这将创建一个新列来存储 tv_episode_number,如果 tv_episode_number 为 NULL,则为 0。然后将其用于具有 tv_series_id、tv_season_number 和名称的唯一索引中。

触发器将设置新列的值并防止插入多个包含空值的相同行或在更新后变得相同。

现在下面的插入语句如果多次运行会抛出重复条目错误:

INSERT INTO main_itemmaster (tv_series_id, name, tv_season_number, tv_episode_number) values (97736, 'Season 4', 4, NULL);

【讨论】:

    【解决方案3】:

    更简单的解决方案(如果您有很多字段,可能为 NULL 并且您仍然希望行具有唯一性)

    1) 添加 unique_hash 字段

    ALTER TABLE `TABLE_NAME_HERE` 
       ADD `unique_hash` BIGINT unsigned NOT NULL DEFAULT '0' COMMENT 'JIRA-ISSUE-ID' AFTER `SOME_EXISTING_FIELD`
       ;
    

    2) 填充它们

    UPDATE TABLE_NAME_HERE SET unique_hash=cast(conv(substring(md5(
        CONCAT(
            IF(field1_id IS NULL, 'NULL',field1_id),'_',
            IF(field2_id IS NULL, 'NULL',field2_id),'_',
            IF(field3_id IS NULL, 'NULL',field3_id),'_',
            IF(field4_id IS NULL, 'NULL',field4_id),'_',
            IF(field5_id IS NULL, 'NULL',field5_id),'_'
        )
    ), 1, 16), 16, 10) as unsigned integer);
    

    3) 在 bigint 上添加唯一索引(它会更快,因为复杂的唯一索引 - 因为它会更小)

    ALTER TABLE `TABLE` ADD UNIQUE (`unique_hash`);
    

    4) 添加触发器

    DELIMITER $$
    CREATE TRIGGER add_unique_hash BEFORE INSERT ON TABLE_NAME_HERE
    FOR EACH ROW
      BEGIN
        SET NEW.unique_hash = (cast(conv(substring(md5(
                                   CONCAT(
                                       IF(NEW.field1_id IS NULL, 'NULL',NEW.field1_id),'_',
                                       IF(NEW.field2_id IS NULL, 'NULL',NEW.field2_id),'_',
                                       IF(NEW.field3_id IS NULL, 'NULL',NEW.field3_id),'_',
                                       IF(NEW.field4_id IS NULL, 'NULL',NEW.field4_id),'_',
                                       IF(NEW.field5_id IS NULL, 'NULL',NEW.field5_id),'_'
                                   )
                               ), 1, 16), 16, 10) as unsigned integer));
      END $$
    DELIMITER ;
    

    5) 尽情享受吧! :)

    您将在 field1_id、field2_id、field3_id、field4_id、field5_id 上拥有真正唯一索引。 NULL 值只允许一次。无需更改脚本。

    【讨论】:

      猜你喜欢
      • 2017-11-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-11-04
      • 2021-11-22
      • 2013-05-15
      • 1970-01-01
      • 2010-09-16
      相关资源
      最近更新 更多