如何实施标签制度


90

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

我当时在考虑一种基本的三表解决方案:有一个tags表,一个articles表和一个tag_to_articles表。

这是解决此问题的最佳解决方案,还是有替代方案?使用这种方法,表的时间将变得非常大,我认为对于搜索而言,效率不是很高。另一方面,快速执行查询并不重要。


Answers:


119

我相信您会在此博客文章中找到有趣的地方:标签:数据库模式

问题:您想要一个数据库架构,可以在其中使用所需的标签数量来标记书签(或博客文章或其他内容)。然后,您要运行查询以将书签限制为标签的并集或交集。您还想从搜索结果中排除(例如:减去)一些标签。

“ MySQLicious”解决方案

在此解决方案中,该模式只有一个表,已对其进行了非规范化。该类型称为“ MySQLicious解决方案”,因为MySQLicious将del.icio.us数据导入具有此结构的表中。

在此处输入图片说明在此处输入图片说明

交叉点(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将其数据组织在两个表中。该表“ scCategories”是“ tag”表,并且具有“ bookmark”表的外键。

在此处输入图片说明

交叉点(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(“书签”,“ 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”,即:书签和webservice,而不是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提出了一个三表结构。通过表格“ tagmap”,书签和标签是n-m相关的。每个标签可以与不同的书签一起使用,反之亦然。这个DB模式也被wordpress使用。查询与“快捷方式”解决方案中的查询完全相同。

在此处输入图片说明

交叉点(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”,即:书签和webservice,而不是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”。


3
该博客文章的作者在此处。该博客不再被Chrome阻止(愚蠢的wordpress漏洞,现已移至tumblr)。将其转换为降价的荣誉
hansaplast

嗨@菲利普 好的,编辑了我的答案。顺便说一句,感谢数据库标签系统方面的出色文章。
Nick Dandoulakis

1
就像一个注释:如果您要在“交叉点查询”中查找Toxi解决方案,并且在搜索“书签”和“ webservice”时也显示书签,则需要将“ HAVING COUNT(b.id)= 3”更改为3到“ sizeof(array('bookmark','webservice'))'”。如果您打算将其用作动态标签查询功能,则仅需稍作详细说明。
毒物

3
文章中提到的用于不同解决方案性能比较的任何链接?
kampta '16

@kampta,不,我没有任何链接。
Nick Dandoulakis '16

8

您的三表解决方案没有错。

另一个选择是限制可以应用于文章的标签数量(例如SO中的5个标签),然后将其直接添加到您的文章表中。

标准化数据库有其优点和缺点,就像将事物硬连接到一个表中一样有优点和缺点。

没有什么可以说你不能两者都做。它与关系数据库范式相违背以重复信息,但是如果目标是性能,则可能必须打破范式。


是的,虽然此方法有一些缺点,但是将标签直接放入商品表肯定是一个选择。如果将5个标签存储在以逗号分隔的字段(如(tag1,2,3,4))中,这将是一个简单的方法。问题是搜索是否会更快。例如,某人想查看带有tag1的所有内容,则必须遍历整个商品表。这将比通过tag_to_article表少。但是话又说回来,tags_to_article表更苗条。另一件事是您每次都要在php中爆炸,我不知道这是否需要时间。
Saif Bechan

如果您同时进行两种操作(带有商品的标签,并在单独的表格中),那么这将为您提供以后为中心的搜索和以标签为中心的搜索性能。权衡是维持重复信息的负担。另外,通过限制标签的数量,您可以将每个标签放入其自己的列中。只需从文章XXXXX处选择*即可;无需爆炸。
约翰

6

建议的三表实现将适用于标记。

堆栈溢出使用不同的实现。他们以纯文本形式将标签存储到帖子表中的varchar列,并使用全文本索引来获取与标签匹配的帖子。例如posts.tags = "algorithm system tagging best-practices"。我确定杰夫已经在某处提到了此事,但​​我忘记了在哪里。


4
这似乎效率极低。标签顺序如何?或相关标签?(例如“过程”类似于“算法”之类的东西)
理查德·杜尔

3

提出的解决方案是我能想到的解决标签和文章之间多对多关系的最佳方法(即使不是唯一可行的方法)。所以我的投票是“是的,它仍然是最好的”。我会对任何其他选择感兴趣。


我同意。这些Tag和TagMap表具有较小的记录大小,并且在正确建立索引后不应显着降低性能。限制每个项目的od标签数量也是一个好主意。
PanJanek

2

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


POstgreSQL仅支持整数数组上的索引:postgresql.org/docs/current/static/intarray.html
Mike Chamberlain 2010年


2

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

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

我的解决方案:

每当创建新标记时,都要使用counter ++(以10为底),然后将该计数器转换为base64。现在,每个标签名称将具有base64 ID。并将此ID和名称一起传递给UI。这样,在我们的系统中创建了4095个标签之前,您将最多拥有两个字符ID。现在,将这些多个标签串联到每个问题表标签列中。还要添加定界符并使它排序。

所以桌子看起来像这样

在此处输入图片说明

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

请注意,单空格定界符是不够的,我们需要双定界符来区分诸如sql和的标记,mysql因为它们LIKE "%sql%"也会返回mysql结果。应该LIKE "%|sql|%"

我知道搜索未建立索引,但您仍可能在与author / dateTime等文章相关的其他列上建立了索引,否则将导致全表扫描。

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


团队,请在评论中提供您对该解决方案缺点的意见。
卡纳加维卢·苏古玛'18

@Nick Dandoulakis请通过提供您对上述解决方案的意见来帮助我吗?
卡纳加维卢·苏古玛,

@JuhaSyrjälä上述解决方案是否还可以?
卡纳加维卢·苏古玛,

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

笔记:

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

相关讨论(对于MySQL):
许多:许多映射表优化
有序列表

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.