您如何设计数据库以支持以下标记功能:
理想情况下,使用单个SQL语句查找使用(至少)一组n个给定标记标记的所有项目。由于要搜索的标签数量以及任何项目上的标签数量未知且可能很高,因此使用JOIN是不切实际的。
有什么想法吗?
感谢目前为止的所有答案。
但是,如果我没有弄错的话,给出的答案显示了如何对标签进行OR搜索。 (选择包含一个或多个n标签的所有项目)。我正在寻找一个有效的AND搜索。 (选择所有包含所有n个标签的项目 - 可能还有更多。)
答案 0 :(得分:72)
这是一篇关于标记数据库模式的好文章:
http://howto.philippkeller.com/2005/04/24/Tags-Database-schemas/
以及性能测试:
http://howto.philippkeller.com/2005/06/19/Tagsystems-performance-tests/
请注意,MySQL的结论非常具体,(至少在2005年编写的时候)具有非常差的全文索引特性。
答案 1 :(得分:19)
关于ANDing:听起来你正在寻找“关系师”操作。 This article以简洁而又易于理解的方式涵盖了关系分工。
关于性能:基于位图的方法直观地听起来很适合这种情况。但是,我不相信“手动”实现位图索引是一个好主意,就像digiguru建议的那样:每当添加新标签时听起来都是一个复杂的情况(?)但是有些DBMS(包括Oracle)提供的位图索引可能会以某种方式因为内置的索引系统可以消除索引维护的潜在复杂性;另外,提供位图索引的DBMS应该能够在执行查询计划时正确考虑它们。
答案 2 :(得分:12)
我没有看到直接解决方案的问题:项目表,标签表,“标记”的交叉表
交叉表上的指数应该足够优化。选择适当的项目将是
SELECT * FROM items WHERE id IN
(SELECT DISTINCT item_id FROM item_tag WHERE
tag_id = tag1 OR tag_id = tag2 OR ...)
AND标记将是
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个,那么这仍然会很重在数据库上。
答案 3 :(得分:12)
我只是想强调@Jeff Atwood链接到(http://howto.philippkeller.com/2005/04/24/Tags-Database-schemas/)的文章是非常彻底的(它讨论了3种不同模式方法的优点)并且通常会对AND查询有一个很好的解决方案。比目前为止所提到的更好(即它不会为每个术语使用相关的子查询)。评论中还有很多好东西。
ps - 每个人在这里谈论的方法在本文中被称为“Toxi”解决方案。
答案 4 :(得分:6)
您可能希望尝试使用非严格数据库解决方案,例如Java Content Repository实施(例如Apache Jackrabbit),并使用基于此构建的搜索引擎,例如Apache Lucene。< / p>
这种具有适当缓存机制的解决方案可能会比本土解决方案产生更好的性能。
但是,我并不认为在小型或中型应用程序中,您需要比先前帖子中提到的规范化数据库更复杂的实现。
编辑:通过您的澄清,在搜索引擎中使用类似JCR的解决方案似乎更具吸引力。从长远来看,这将极大地简化您的程序。答案 5 :(得分:5)
最简单的方法是创建标记表
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]
答案 6 :(得分:1)
我的第二个@Zizzencs建议你可能想要一些不完全(R)以数据库为中心的东西
不知何故,我相信使用普通的nvarchar字段来存储带有适当缓存/索引的标记可能会产生更快的结果。但那只是我。
我使用3个表来实现标记系统以表示之前的多对多关系(Item Tags ItemTags),但我想你会在很多地方处理标记,我可以告诉你3必须同时操作/查询的表肯定会使您的代码更复杂。
您可能需要考虑增加的复杂性是否值得。
答案 7 :(得分:0)
您将无法避免加入,仍然会有所规范。
我的方法是有一个标签表。
TagId (PK)| TagName (Indexed)
然后,您的项目表中有一个TagXREFID列。
这个TagXREFID列是第3个表的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等...并动态构建查询。
答案 8 :(得分:0)
我喜欢做的是有许多代表原始数据的表格,所以在这种情况下你有
Items (ID pk, Name, <properties>)
Tags (ID pk, Name)
TagItems (TagID fk, ItemID fk)
这适用于写入时间,并保持所有标准化,但您可能还注意到,对于每个标记,您需要为要进行AND的每个其他标记连接两次表,因此读取速度很慢。
改进读取的解决方案是通过设置存储过程来创建一个命令缓存表,该存储过程实质上创建了一个表示扁平格式数据的新表...
CachedTagItems(ID, Name, <properties>, tag1, tag2, ... tagN)
然后,您可以考虑Tagged Item表需要保持最新的频率,如果它在每个插入上,则在游标插入事件中调用存储过程。如果这是一个小时的任务,那么设置一个每小时的工作来运行它。
现在要真正聪明地进行数据检索,您需要创建一个存储过程来从标记中获取数据。您希望传入一个包含要从数据库中选择的标记列表的参数,而不是在大量的case语句中使用嵌套查询,并返回一组记录集。使用按位运算符,这在二进制格式中是最好的。
以二进制格式,很容易解释。假设有四个标签要分配给一个项目,二进制文件中我们可以表示
0000
如果将所有四个标签分配给一个对象,该对象将如下所示......
1111
如果只是前两个......
1100
然后,只是在您想要的列中找到带有1和0的二进制值的情况。使用SQL Server的Bitwise运算符,您可以使用非常简单的查询检查第一列中是否存在1。
点击此链接查找more。
答案 9 :(得分:0)
用其他人所说的话来说:诀窍不在架构中,而是在查询中。
实体/标签/标签的天真模式是正确的方法。但正如您所见,目前还不清楚如何使用大量标签执行AND查询。
优化该查询的最佳方法是依赖于平台,因此我建议您使用RDBS重新标记您的问题,并将标题更改为“在标记数据库上执行和查询的最佳方式”。
我对MS SQL有一些建议,但如果不是你正在使用的平台,我会克制。
答案 10 :(得分:0)
上述答案的一个变体是使用标记ID,对它们进行排序,组合为^分隔的字符串并对它们进行哈希处理。 然后简单地将哈希与项目相关联。每个标签组合都会产生一个新密钥。要进行AND搜索,只需使用给定的标记ID和搜索重新创建哈希。 更改项目上的标记将导致重新创建哈希。具有相同标记集的项共享相同的散列键。
答案 11 :(得分:0)
如果您是数组类型,则可以预先聚合所需的数据。在另一个帖子中看到这个答案: