使用非唯一列避免mySQL表中的重复条目

时间:2014-01-25 13:58:09

标签: php mysql sql database-schema

我正在为私人网站开发CMS系统(主要是作为学习练习)。 Atm我有三个表:一个用于文章,一个用于标签和一个连接表,以便每篇文章可以有多个标签。

我遇到问题的表格包含三列 -

article_tags: id (auto_increment), article_id, tag_id

我的问题源于这样一个事实:一篇文章可以出现任意次,而且一个标签也可以出现任意次,但是两者的给定组合应该只出现一次 - 也就是说,每篇文章应该只有对任何单个标签的一个引用。目前可以插入id不同的“重复”行,但article_id和tag_id的组合是相同的:

id , article_id, tag_id
1       1           1
2       1           2    
3       2           1    
4       1           1    <- this is wrong

我可以在PHP代码中检查包含这种组合的记录,但是如果可能的话,我宁愿在sql中这样做(如果不是,或者不希望我使用PHP来做)。由于id不同而且无法设置唯一列,例如INSERT IGNORE和ON DUPLICATE不起作用。

我对mySQL很陌生,所以如果我做些傻事,请指出我正确的方向。

由于

2 个答案:

答案 0 :(得分:3)

这种多对多关系表(有时称为连接表)通常只有两列,并且主键是两者的组合。

  article_id
  tag_id
  pk = (article_id, tag_id)

如果您更改该表的定义,您将明确解决该问题。

如何在复合键中订购列?这取决于您的应用程序如何在连接表中查找项目。如果你总是从article_id开始并查找tag_id,那么你首先将article_id放在键中。 DBMS可以随机访问密钥中第一列的值,但必须扫描索引以在密钥的第二列(或后续列)中查找值。

您可能希望在表(tag_id, article_id)上创建第二个索引。这将允许基于tag_id的快速查找。你可能会问,“为什么还要把两列放在索引中呢?”这是为了使索引成为覆盖索引。在覆盖索引中,可以直接从索引中检索所需的值。例如,使用覆盖索引

 SELECT article_id FROM article_tag WHERE tag_id = 12345

(或使用类似查找逻辑的JOIN)只需访问磁盘驱动器上的索引即可获得结果。如果您没有覆盖索引,则查询需要从索引跳转到数据表,这是一个额外的步骤。

连接表通常具有非常短的行(几个整数),因此几个覆盖索引(主键和额外索引)的重复数据不是一个大的磁盘空间。

答案 1 :(得分:3)

您应该查看表格定义。

你可以(从最佳到最差):

  1. 在(article_id和tag_id)上添加复合主键并删除auto_increment(上一个主键)
  2. 在(article_id和tag_id)上添加索引(UNIQUE)并保留auto_increment主键
  3. 在php中选择distinct:SELECT DISTINCT(article_id, tag_id) FROM ...而不更改表格中的任何内容
  4. 现在,您的表定义如下:

    CREATE TABLE IF NOT EXISTS `article_tags` (
      `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
      `article_id` int(11) NOT NULL,
      `tag_id` int(11) NOT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
    

    最佳解决方案(选项1)是删除当前(auto_increment)主键并在列article_id和tag_id上添加主键(复合):

    CREATE TABLE IF NOT EXISTS `article_tags` (
      `article_id` int(11) NOT NULL,
      `tag_id` int(11) NOT NULL,
      PRIMARY KEY (`article_id`,`tag_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
    

    但是(选项2)如果您绝对想要保留auto_increment主键,请在列上添加索引(唯一):

    CREATE TABLE IF NOT EXISTS `article_tags` (
      `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
      `article_id` int(11) NOT NULL,
      `tag_id` int(11) NOT NULL,
      PRIMARY KEY (`id`),
      UNIQUE KEY `article_id` (`article_id`,`tag_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
    

    无论如何,如果您不想更改表定义,可以在php查询中始终使用DISTINCT:

    SELECT DISTINCT(article_id, tag_id) FROM article_tags