如何实现标签系统

时间:2009-11-27 19:35:39

标签: algorithm system tagging

我想知道实现标签系统的最佳方法是什么,就像SO上使用的标签系统一样。我正在考虑这个问题,但我无法提出一个很好的可扩展解决方案。

我正在考虑使用基本的3表解决方案:拥有tags表,articles表和tag_to_articles表。

这是解决这个问题的最佳解决方案,还是有替代方案?使用这种方法,表格会在时间上变得非常大,而且对于搜索而言,我认为这不是太有效。另一方面,查询执行速度并不重要。

7 个答案:

答案 0 :(得分:110)

我相信你会发现这篇博文很有意思:Tags: Database schemas

  

问题:您希望拥有一个可以标记a的数据库架构   书签(或博客文章或其他任何内容),包含任意数量的标签。   之后,您希望运行查询以将书签限制为   联合或标记的交集。你也想排除(比如说:减去)   搜索结果中的一些标签。

“MySQLicious”解决方案

在此解决方案中,架构只有一个表,它是非规范化的。这种类型称为“MySQLicious解决方案”,因为MySQLicious将del.icio.us数据导入具有此结构的表中。

enter image description here enter image description here

交叉点(AND) 查询“search + webservice + semweb”:

SELECT *
FROM `delicious`
WHERE tags LIKE "%search%"
AND tags LIKE "%webservice%"
AND tags LIKE "%semweb%"

联盟(OR) 查询“search | webservice | semweb”:

SELECT *
FROM `delicious`
WHERE tags LIKE "%search%"
OR tags LIKE "%webservice%"
OR tags LIKE "%semweb%"

<强>减 查询“search + webservice-semweb”

SELECT *
FROM `delicious`
WHERE tags LIKE "%search%"
AND tags LIKE "%webservice%"
AND tags NOT LIKE "%semweb%"

“Scuttle”解决方案

Scuttle将其数据组织在两个表格中。那个表“scCategories”是“标签”表,并且有一个“书签”表的外键。

enter image description here

交叉点(AND) 查询“bookmark + webservice + semweb”:

SELECT b.*
FROM scBookmarks b, scCategories c
WHERE c.bId = b.bId
AND (c.category IN ('bookmark', 'webservice', 'semweb'))
GROUP BY b.bId
HAVING COUNT( b.bId )=3

首先,搜索所有书签 - 标签组合,其中标签是“书签”,“webservice”或“semweb”(c.category IN('bookmark','webservice','semweb')),然后只是搜索了所有三个标签的书签都被考虑在内(HAVING COUNT(b.bId)= 3)。

联盟(OR) 查询“bookmark | webservice | semweb”: 只要省略HAVING子句就可以了:

SELECT b.*
FROM scBookmarks b, scCategories c
WHERE c.bId = b.bId
AND (c.category IN ('bookmark', 'webservice', 'semweb'))
GROUP BY b.bId

减(排除) 查询“bookmark + webservice-semweb”,即:bookmark AND webservice AND NOT semweb。

SELECT b. *
FROM scBookmarks b, scCategories c
WHERE b.bId = c.bId
AND (c.category IN ('bookmark', 'webservice'))
AND b.bId NOT
IN (SELECT b.bId FROM scBookmarks b, scCategories c WHERE b.bId = c.bId AND c.category = 'semweb')
GROUP BY b.bId
HAVING COUNT( b.bId ) =2

退出HAVING COUNT会导致查询“bookmark | webservice-semweb”。


“Toxi”解决方案

Toxi想出了一个三表结构。通过表“tagmap”,书签和标签是n-to-m相关的。每个标签可以与不同的书签一起使用,反之亦然。 wordpress也使用这个DB模式。 查询与“scuttle”解决方案完全相同。

enter image description here

交叉点(AND) 查询“bookmark + webservice + semweb”

SELECT b.*
FROM tagmap bt, bookmark b, tag t
WHERE bt.tag_id = t.tag_id
AND (t.name IN ('bookmark', 'webservice', 'semweb'))
AND b.id = bt.bookmark_id
GROUP BY b.id
HAVING COUNT( b.id )=3

联盟(OR) 查询“bookmark | webservice | semweb”

SELECT b.*
FROM tagmap bt, bookmark b, tag t
WHERE bt.tag_id = t.tag_id
AND (t.name IN ('bookmark', 'webservice', 'semweb'))
AND b.id = bt.bookmark_id
GROUP BY b.id

减(排除) 查询“bookmark + webservice-semweb”,即:bookmark AND webservice AND NOT semweb。

SELECT b. *
FROM bookmark b, tagmap bt, tag t
WHERE b.id = bt.bookmark_id
AND bt.tag_id = t.tag_id
AND (t.name IN ('Programming', 'Algorithms'))
AND b.id NOT IN (SELECT b.id FROM bookmark b, tagmap bt, tag t WHERE b.id = bt.bookmark_id AND bt.tag_id = t.tag_id AND t.name = 'Python')
GROUP BY b.id
HAVING COUNT( b.id ) =2

退出HAVING COUNT会导致查询“bookmark | webservice-semweb”。

答案 1 :(得分:8)

你的三桌解决方案没有错。

另一种选择是限制可以应用于文章的标签数量(如SO中的5)并将这些标签直接添加到文章表中。

规范化数据库有其优点和缺点,就像硬连接到一个表中有利有弊。

没有什么说你不能做到这两点。它反对关系数据库范式重复信息,但如果目标是性能,你可能不得不打破范式。

答案 2 :(得分:6)

您建议的三个表格实施将用于标记。

然而,

堆栈溢出使用不同的实现。它们以明文形式将标记存储到posts表中的varchar列,并使用全文索引来获取与标记匹配的帖子。例如posts.tags = "algorithm system tagging best-practices"。我确信Jeff已经在某个地方提到了这个但我忘记了。

答案 3 :(得分:3)

建议的解决方案是最好的 - 如果不是唯一可行的方法,我可以想到解决标签和文章之间的多对多关系。所以我的投票是“是的,它仍然是最好的。”我会对任何替代方案感兴趣。

答案 4 :(得分:2)

如果您的数据库支持可索引数组(例如PostgreSQL),我建议使用完全非规范化的解决方案 - 将标记存储为同一个表上的字符串数组。如果不是,则将对象映射到标记的辅助表是最佳解决方案。如果您需要针对标记存储额外信息,则可以使用单独的标记表,但是为每个标记查找引入第二个连接没有意义。

答案 5 :(得分:2)

我想建议优化MySQLicious以获得更好的性能。 在此之前,Toxi(3表)解决方案的缺点是

如果您有数百万个问题,并且每个问题都有5个标签,那么tagmap表中将有500万个条目。因此,首先我们必须根据标签搜索过滤出1万个标签图条目,然后再次过滤出那些万标记的匹配问题。因此,当过滤掉artical id是简单的数字时,它就可以了,但是如果它是UUID(32 varchar),那么过滤掉需要更大的比较,尽管它被索引。

我的解决方案:

每当创建新标记时,都有计数器++(基数为10),并将该计数器转换为base64。现在每个标签名称都有base64 id。并将此ID与名称一起传递给UI。 这样,在我们的系统中创建了4095个标签之前,您将拥有最多两个字符。现在将这些多个标记连接到每个问题表标记列中。添加分隔符并对其进行排序。

所以表格看起来像这样

enter image description here

在查询时,查询id而不是真实标签名称。 由于 SORTED ,标记上的and条件将更有效(LIKE '%|a|%|c|%|f|%)。

请注意,单个空格分隔符是不够的,我们需要使用双分隔符来区分sqlmysql等标记,因为LIKE "%sql%"也会返回mysql个结果。应为LIKE "%|sql|%"

我知道搜索没有编入索引但仍然可能已经编入了与作者/ dateTime等文章相关的其他列的索引,否则将导致全表扫描。

最后使用此解决方案,不需要内部连接,其中必须将百万条记录与连接条件下的500万条记录进行比较。

答案 6 :(得分:0)

CREATE TABLE Tags (
    tag VARHAR(...) NOT NULL,
    bid INT ... NOT NULL,
    PRIMARY KEY(tag, bid),
    INDEX(bid, tag)
)

注意:

  • 这比TOXI更好,因为它没有经过额外的many:man表,这使得优化很困难。
  • 当然,由于冗余标签的存在,我的方法可能比TOXI稍大一些,但这只占整个数据库的一小部分,并且性能提高可能会很明显。
  • li>
  • 它具有高度的可扩展性。
  • 它没有(因为不需要)替代AUTO_INCREMENT PK。因此,它比Scuttle好。
  • MySQLicious很烂,因为它无法使用索引({<1>前导通配符使用LIKE;对子字符串的错误命中)
  • 对于MySQL,请确保使用ENGINE = InnoDB,以获得“聚类”效果。

相关讨论(对于MySQL):
many:many mapping table optimization
ordered lists