首页 文章

标记数据库设计

提问于
浏览
152

您将如何设计数据库以支持以下标记功能:

  • 项可以包含大量标签

  • 搜索所有使用给定标记集标记的项目必须快速(项目必须包含所有标记,因此它是AND搜索,而不是OR搜索)

  • 创建/写入项目可能较慢以启用快速查找/读取

理想情况下,使用单个SQL语句查找使用(至少)一组n个给定标记标记的所有项目 . 由于要搜索的标签数量以及任何项目上的标签数量未知且可能很高,因此使用JOIN是不切实际的 .

有任何想法吗?


谢谢你到目前为止的所有答案 .

但是,如果我没有弄错的话,给出的答案显示了如何对标签进行OR搜索 . (选择包含一个或多个n标签的所有项目) . 我正在寻找一个有效的AND搜索 . (选择所有包含所有n个标签的项目 - 可能还有更多 . )

12 回答

  • 0

    关于ANDing:听起来你正在寻找"relational division"操作 . This article以简洁而又易于理解的方式涵盖了关系分工 .

    关于性能:基于位图的方法直观地听起来很适合这种情况 . 但是,我不相信“手动”实现位图索引是个好主意,就像digiguru建议的那样:每当添加新标签时听起来都是一个复杂的情况(?)但是有些DBMS(包括Oracle)提供的位图索引可能会以某种方式因为内置的索引系统可以消除索引维护的潜在复杂性;另外,提供位图索引的DBMS应该能够在执行查询计划时正确考虑它们 .

  • 72

    这是一篇关于标记数据库模式的好文章:

    http://howto.philippkeller.com/2005/04/24/Tags-Database-schemas/

    以及性能测试:

    http://howto.philippkeller.com/2005/06/19/Tagsystems-performance-tests/

    请注意,MySQL的结论非常具体,(至少在2005年编写的时候)具有非常差的全文索引特性 .

  • 0

    我没有看到直接解决方案的问题:项目表,标签表,“标记”的交叉表

    交叉表上的指数应该足够优化 . 选择合适的项目即可

    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个,那么这仍然会很重在数据库上 .

  • 4

    我只是想强调@Jeff Atwood链接到(http://howto.philippkeller.com/2005/04/24/Tags-Database-schemas/)的文章是非常彻底的(它讨论了3种不同模式方法的优点),并且对于通常比上面提到的更好的AND查询有一个很好的解决方案 . 到目前为止(即它不使用每个术语的相关子查询) . 评论中还有很多好东西 .

    ps - 每个人在这里谈论的方法在本文中被称为“Toxi”解决方案 .

  • 0

    您可能希望尝试一种非严格的数据库解决方案,例如Java Content Repository实现(例如Apache Jackrabbit),并使用基于此构建的搜索引擎,如Apache Lucene .

    具有适当缓存机制的该解决方案可能比本地解决方案产生更好的性能 .

    但是,我并不认为在小型或中型应用程序中,您需要比先前帖子中提到的规范化数据库更复杂的实现 .

    编辑:通过您的澄清,使用类似JCR的搜索引擎解决方案似乎更具吸引力 . 从长远来看,这将极大地简化您的程序 .

  • 1

    最简单的方法是创建 tags 表 .
    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]
    

    UPDATE
    根据您对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]
    
  • 12

    我的第二个@Zizzencs建议你可能想要一些不完全(R)以数据库为中心的东西

    不知何故,我相信使用普通的nvarchar字段来存储一些正确的标签缓存/索引可能会产生更快的结果 . 但那只是我 .

    我已经使用3个表来实现标记系统以表示之前的多对多关系(Item Tags ItemTags),但我想你会在很多地方处理标记,我可以告诉你,有3个表必须被同时操纵/查询肯定会使你的代码更复杂 .

    您可能想要考虑增加的复杂性是否值得 .

  • 18

    你将无法避免加入,仍然有点规范化 .

    我的方法是有一个Tag Table .

    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等...并动态构建查询 .

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

  • 11

    用其他人所说的话来说:诀窍不在 schema 中,而是在 query 中 .

    实体/标签/标签的天真模式是正确的方法 . 但正如您所看到的,目前还不清楚如何使用大量标签执行AND查询 .

    优化该查询的最佳方法是依赖于平台,因此我建议您使用RDBS重新标记您的问题,并将 Headers 更改为“在标记数据库上执行和查询的最佳方式” .

    我对MS SQL有一些建议,但如果不是你正在使用的平台,我会克制 .

  • 0

    上述答案的一个变体是使用标记ID,对它们进行排序,组合为^分隔的字符串并对它们进行哈希处理 . 然后简单地将哈希与项目相关联 . 每个标签组合都会产生一个新密钥 . 要进行AND搜索,只需使用给定的标记ID和搜索重新创建哈希 . 更改项目上的标记将导致重新创建哈希 . 具有相同标签集的项共享相同的散列键 .

  • 6

    如果您是数组类型,则可以预先聚合所需的数据 . 在另一个帖子中看到这个答案:

    what's the utility of array type?

相关问题