我想知道实现标签系统的最佳方法是什么,就像SO上使用的那样。我在想这个,但是我无法提出一个好的可扩展解决方案。
我当时在考虑一种基本的三表解决方案:有一个tags
表,一个articles
表和一个tag_to_articles
表。
这是解决此问题的最佳解决方案,还是有替代方案?使用这种方法,表的时间将变得非常大,我认为对于搜索而言,效率不是很高。另一方面,快速执行查询并不重要。
Answers:
我相信您会在此博客文章中找到有趣的地方:标签:数据库模式
问题:您想要一个数据库架构,可以在其中使用所需的标签数量来标记书签(或博客文章或其他内容)。然后,您要运行查询以将书签限制为标签的并集或交集。您还想从搜索结果中排除(例如:减去)一些标签。
在此解决方案中,该模式只有一个表,已对其进行了非规范化。该类型称为“ 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”。
您的三表解决方案没有错。
另一个选择是限制可以应用于文章的标签数量(例如SO中的5个标签),然后将其直接添加到您的文章表中。
标准化数据库有其优点和缺点,就像将事物硬连接到一个表中一样有优点和缺点。
没有什么可以说你不能两者都做。它与关系数据库范式相违背以重复信息,但是如果目标是性能,则可能必须打破范式。
建议的三表实现将适用于标记。
堆栈溢出使用不同的实现。他们以纯文本形式将标签存储到帖子表中的varchar列,并使用全文本索引来获取与标签匹配的帖子。例如posts.tags = "algorithm system tagging best-practices"
。我确定杰夫已经在某处提到了此事,但我忘记了在哪里。
如果您的数据库支持可索引数组(例如PostgreSQL),则建议使用完全非规范化的解决方案-将标签存储为字符串表在同一张表上。如果没有,那么将对象映射到标签的辅助表是最佳解决方案。如果您需要存储有关标签的额外信息,则可以使用单独的标签表,但是没有必要为每个标签查找引入第二个联接。
我想建议优化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万条记录进行比较。
CREATE TABLE Tags (
tag VARHAR(...) NOT NULL,
bid INT ... NOT NULL,
PRIMARY KEY(tag, bid),
INDEX(bid, tag)
)
笔记:
AUTO_INCREMENT
PK。因此,它比Scuttle好。LIKE
使用前导通配符;对子字符串的错误命中)相关讨论(对于MySQL):
许多:许多映射表优化
有序列表