题
您将如何设计数据库来支持以下标记功能:
- 项目可以有大量标签
- 搜索带有给定标签集的所有项目必须快速(项目必须具有所有标签,因此它是 AND 搜索,而不是 OR 搜索)
- 创建/写入项目可能会较慢,以实现快速查找/读取
理想情况下,应使用单个 SQL 语句来查找标记有(至少)一组 n 个给定标签的所有项目。由于要搜索的标签数量以及任何项目上的标签数量未知并且可能很高,因此使用 JOIN 是不切实际的。
有任何想法吗?
感谢到目前为止所有的答案。
但是,如果我没有记错的话,给出的答案显示了如何对标签进行“或”搜索。(选择具有 n 个标签中的一个或多个的所有项目)。我正在寻找有效的 AND 搜索。(选择具有所有 n 个标签的所有项目 - 甚至可能更多。)
解决方案
关于与运算:听起来您正在寻找“关系除法”操作。 本文 以简洁易懂的方式涵盖了关系划分。
关于性能:基于位图的方法直观上听起来很适合这种情况。然而,我不相信“手动”实现位图索引是一个好主意,就像 digiguru 建议的那样:每当添加新标签时,这听起来都是一个复杂的情况(?)但是一些 DBMS(包括 Oracle)提供位图索引,这可能在某种程度上有用,因为内置索引系统消除了索引维护的潜在复杂性;此外,提供位图索引的 DBMS 应该能够在执行查询计划时正确考虑它们。
其他提示
这是一篇关于标记数据库模式的好文章:
http://howto.philippkeller.com/2005/04/24/Tags-Database-schemas/
以及性能测试:
http://howto.philippkeller.com/2005/06/19/Tagsystems-performance-tests/
请注意,其中的结论非常针对 MySQL(至少在 2005 年撰写本文时),其全文索引特性非常差。
我认为简单的解决方案没有问题:项目表、标签表、“标记”交叉表
交叉表上的索引应该足够优化。选择适当的项目将是
SELECT * FROM items WHERE id IN
(SELECT DISTINCT item_id FROM item_tag WHERE
tag_id = tag1 OR tag_id = tag2 OR ...)
并且标记将是
SELECT * FROM items WHERE
EXISTS (SELECT 1 FROM item_tag WHERE id = item_id AND tag_id = tag1)
AND EXISTS (SELECT 1 FROM item_tag WHERE id = item_id AND tag_id = tag2)
AND ...
不可否认,这对于大量比较标签来说效率不高。如果要在内存中维护标签计数,则可以从不常见的标签开始查询,这样 AND 序列的评估速度会更快。根据要匹配的标签的预期数量以及匹配其中任何一个标签的预期,这可能是一个不错的解决方案,如果您要匹配 20 个标签,并期望某个随机项目将匹配其中的 15 个,那么这仍然会很重在数据库上。
我只是想强调@Jeff Atwood 链接到的文章(http://howto.philippkeller.com/2005/04/24/Tags-Database-schemas/)非常彻底(它讨论了 3 种不同模式方法的优点),并且对于 AND 查询有一个很好的解决方案,通常比到目前为止这里提到的(即它不为每个术语使用相关子查询)。评论里也有很多好东西。
ps - 大家在这里谈论的方法在文章中被称为“Toxi”解决方案。
最简单的方法是创建一个 标签 桌子。
Target_Type
-- 如果您要标记多个表
Target
-- 被标记记录的关键
Tag
-- 标签的文本
查询数据类似于:
Select distinct target from tags
where tag in ([your list of tags to search for here])
and target_type = [the table you're searching]
更新
根据您对 AND 条件的要求,上面的查询将变成这样
select target
from (
select target, count(*) cnt
from tags
where tag in ([your list of tags to search for here])
and target_type = [the table you're searching]
)
where cnt = [number of tags being searched]
我同意 @Zizzencs 建议,您可能想要一些不完全以 (R)DB 为中心的东西
不知何故,我相信使用普通的 nvarchar 字段来存储具有适当缓存/索引的标签可能会产生更快的结果。但这只是我。
我之前已经使用 3 个表来表示多对多关系(Item Tags ItemTags)实现了标记系统,但我想您将在很多地方处理标签,我可以告诉您,使用 3 个表必须一直同时操作/查询肯定会让你的代码变得更加复杂。
您可能需要考虑增加的复杂性是否值得。
您将无法避免连接并且仍然在某种程度上标准化。
我的方法是有一个标签表。
TagId (PK)| TagName (Indexed)
然后,您的项目表中有一个 TagXREFID 列。
此 TagXREFID 列是第三个表的 FK,我将其称为 TagXREF:
TagXrefID | ItemID | TagId
因此,要获取某个项目的所有标签,类似于:
SELECT Tags.TagId,Tags.TagName
FROM Tags,TagXref
WHERE TagXref.TagId = Tags.TagId
AND TagXref.ItemID = @ItemID
要获取标签的所有项目,我会使用如下内容:
SELECT * FROM Items, TagXref
WHERE TagXref.TagId IN
( SELECT Tags.TagId FROM Tags
WHERE Tags.TagName = @TagName; )
AND Items.ItemId = TagXref.ItemId;
要将一堆标签与在一起,您需要稍微修改上面的语句以添加 AND Tags.TagName = @TagName1 AND Tags.TagName = @TagName2 等...并动态构建查询。
我喜欢做的是有一些代表原始数据的表,所以在这种情况下你会有
Items (ID pk, Name, <properties>)
Tags (ID pk, Name)
TagItems (TagID fk, ItemID fk)
这对于写入时间来说速度很快,并且使所有内容都标准化,但您可能还注意到,对于每个标签,您需要为每个想要 AND 的其他标签连接表两次,因此读取速度很慢。
改进读取的解决方案是通过设置一个存储过程来根据命令创建一个缓存表,该存储过程本质上是创建以扁平格式表示数据的新表......
CachedTagItems(ID, Name, <properties>, tag1, tag2, ... tagN)
然后您可以考虑标记项表需要多久保持最新(如果在每次插入时都需要),然后在游标插入事件中调用存储过程。如果它是每小时任务,则设置每小时作业来运行它。
现在,要真正巧妙地进行数据检索,您需要创建一个存储过程来从标签中获取数据。您不想在大量 case 语句中使用嵌套查询,而是希望传递包含要从数据库中选择的标签列表的单个参数,并返回项目的记录集。最好采用二进制格式,使用按位运算符。
以二进制格式来说,很容易解释。假设有四个标签要分配给一个项目,我们可以用二进制表示
0000
如果所有四个标签都分配给一个对象,则该对象将如下所示......
1111
如果只是前两个...
1100
那么这只是在您想要的列中查找带有 1 和 0 的二进制值的情况。使用 SQL Server 的按位运算符,您可以使用非常简单的查询来检查第一列中是否有 1。
检查此链接以了解详情 更多的.
套用别人说过的话:诀窍不在 图式, ,它在 询问.
实体/标签/标签的朴素模式是正确的方法。但正如您所看到的,目前还不清楚如何使用大量标签执行 AND 查询。
优化该查询的最佳方法将取决于平台,因此我建议使用 RDBS 重新标记您的问题,并将标题更改为“在标记数据库上执行 AND 查询的最佳方法”之类的内容。
我对 MS SQL 有一些建议,但如果这不是您正在使用的平台,我会避免这样做。
上述答案的一个变体是获取标签 ID,对它们进行排序,组合为 ^ 分隔的字符串并对它们进行哈希处理。然后只需将哈希值与该项目相关联即可。每个标签组合都会产生一个新密钥。要进行 AND 搜索,只需使用给定的标签 ID 重新创建哈希并进行搜索即可。更改项目上的标签将导致重新创建哈希值。具有相同标签集的项目共享相同的哈希键。
如果您有数组类型,则可以预先聚合所需的数据。在单独的线程中查看此答案: