2014-01-25 37 views
0

我正在为私人网站开发CMS系统(主要是作为学习练习)。 Atm我有三个表格:一个用于文章,一个用于标签和连接表格,以便每篇文章可以有多个标签。避免在具有非唯一列的mySQL表中重复条目

我有问题,与由三列的表格 -

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非常陌生,所以如果我在做一些愚蠢的事情,请点我正确的方向。

感谢

回答

3

您应该查看您的表格定义。

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

  1. 添加上(的article_id和TAG_ID)复合主键和删除AUTO_INCREMENT(以前的主键)
  2. 上(article_id的添加一个索引(UNIQUE)和TAG_ID),并保持您的auto_increment主键
  3. 选择PHP不同:SELECT DISTINCT(article_id, tag_id) FROM ...没有现在在你的餐桌

改变任何东西,你的表被定义为是这样的:

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; 

无论如何,如果你不想改变你的表definitio n,你可以随时在你的php查询中使用DISTINCT:

SELECT DISTINCT(article_id, tag_id) FROM article_tags 
+0

非常简洁的答案。如果我理解正确#3是最差的,因为它会增加SELECT查询的开销? #2更糟糕,因为它基本上和#1一样,但是在旧的auto_increment形式中有一个额外的(不必要的?)列? – ProFishChris

+0

不再简洁。看到我编辑的答案 –

+0

虽然更详细!对不起,我感到困惑,但我更感兴趣的是你为什么按照你的方式命令他们,而不是如何实现他们 – ProFishChris

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使用类似查找逻辑)仅需要访问磁盘驱动器上的索引来获取结果。如果您没有覆盖索引,则查询需要从索引跳转到数据表,这是一个额外的步骤。

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

+0

这正是我寻找的那种优雅的解决方案。复合键并不是我在我的经验中遇到的 - 当然是有限的。综合指数的顺序有没有明显的区别,还是没有关系? – ProFishChris

+0

@ProFishChris,请参阅我的编辑。好问题。 –

+0

哇,这比我想象的要多得多。十分有趣。感谢您澄清 – ProFishChris