Algorithm 如何实现标签系统

Algorithm 如何实现标签系统,algorithm,system,tagging,Algorithm,System,Tagging,我想知道最好的方法是实现一个标签系统,就像在SO上使用的那样。我在考虑这个问题,但是我不能想出一个好的可扩展的解决方案 我在考虑一个基本的3表解决方案:一个标签表,一个文章表和一个标签到文章表 这是解决这个问题的最好办法,还是有其他办法?使用这种方法,表在时间上会变得非常大,我认为对于搜索来说,这不是很有效。另一方面,快速执行查询并不重要。建议的解决方案是解决标记和文章之间多对多关系的最佳方法(如果不是唯一可行的话)。因此,我投的票是“是的,它仍然是最好的。”不过,我对任何替代方案都感兴趣。您的

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

我在考虑一个基本的3表解决方案:一个
标签
表,一个
文章
表和一个
标签到文章


这是解决这个问题的最好办法,还是有其他办法?使用这种方法,表在时间上会变得非常大,我认为对于搜索来说,这不是很有效。另一方面,快速执行查询并不重要。

建议的解决方案是解决标记和文章之间多对多关系的最佳方法(如果不是唯一可行的话)。因此,我投的票是“是的,它仍然是最好的。”不过,我对任何替代方案都感兴趣。

您的三表解决方案没有问题

另一个选项是限制可应用于文章的标记数(如SO中的5个),并将这些标记直接添加到文章表中

规范化DB有其优点和缺点,就像将事物硬连接到一个表中有优点和缺点一样


没有人说你不能两者兼得。重复信息不符合关系数据库范式,但如果目标是性能,您可能必须打破范式。

我相信您会发现这篇博文很有趣:

问题是:您希望有一个数据库模式,可以在其中标记 书签(或博客帖子或其他任何东西)包含您想要的任意多个标签。 然后,您希望运行查询以将书签约束到 标记的并集或交集。您还希望排除(例如:负) 搜索结果中的一些标记

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

交叉点(和) 查询“搜索+网络服务+semweb”:

SELECT *
FROM `delicious`
WHERE tags LIKE "%search%"
AND tags LIKE "%webservice%"
AND tags LIKE "%semweb%"
SELECT *
FROM `delicious`
WHERE tags LIKE "%search%"
OR tags LIKE "%webservice%"
OR tags LIKE "%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
联合(或) 查询“搜索| Web服务| semweb”:

SELECT *
FROM `delicious`
WHERE tags LIKE "%search%"
AND tags LIKE "%webservice%"
AND tags LIKE "%semweb%"
SELECT *
FROM `delicious`
WHERE tags LIKE "%search%"
OR tags LIKE "%webservice%"
OR tags LIKE "%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
负 查询“搜索+网络服务semweb”

SELECT *
FROM `delicious`
WHERE tags LIKE "%search%"
AND tags LIKE "%webservice%"
AND tags NOT LIKE "%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
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

“天窗”解决方案 将其数据组织在两个表中。该表“scCategories”是“标记”表,并且具有指向“书签”表的外键

交叉点(和) 查询“书签+webservice+semweb”:

SELECT *
FROM `delicious`
WHERE tags LIKE "%search%"
AND tags LIKE "%webservice%"
AND tags LIKE "%semweb%"
SELECT *
FROM `delicious`
WHERE tags LIKE "%search%"
OR tags LIKE "%webservice%"
OR tags LIKE "%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
首先,搜索所有书签标记组合,其中标记为“bookmark”、“webservice”或“semweb”(c.category IN('bookmark'、'webservice'、'semweb')),然后只考虑已搜索所有三个标记的书签(计数(b.bId)=3)

联合(或) 查询“书签| Web服务| 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
负(排除) 查询“书签+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
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”。

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
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

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

交叉点(和) 查询“书签+webservice+semweb”

SELECT *
FROM `delicious`
WHERE tags LIKE "%search%"
AND tags LIKE "%webservice%"
AND tags NOT LIKE "%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
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
联合(或) 查询“书签| Web服务| semweb”

SELECT *
FROM `delicious`
WHERE tags LIKE "%search%"
AND tags LIKE "%webservice%"
AND tags NOT LIKE "%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
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
负(排除) 查询“书签+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
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”。

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

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

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
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

但是,堆栈溢出使用不同的实现。它们以纯文本形式将标记存储到posts表中的varchar列,并使用全文索引获取与标记匹配的POST。例如
posts.tags=“算法系统标记最佳实践”
。我确信Jeff在某个地方提到过这一点,但我忘记了在哪里。

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

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

我的解决方案:

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

所以这张桌子看起来像这样

查询时,查询id而不是实际的标记名。 由于它是排序的
和标记上的
条件将更有效(
类似“%| a |%| c |%| f |%

请注意,单个空格分隔