【问题标题】:Database Design for Tagging [closed]用于标记的数据库设计[关闭]
【发布时间】:2010-09-08 02:07:10
【问题描述】:

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

  • 项目可以有大量标签
  • 必须快速搜索使用给定标签集标记的所有项目(项目必须具有所有标签,因此它是 AND 搜索,而不是 OR 搜索)
  • 创建/写入项目可能会更慢以实现快速查找/读取

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

有什么想法吗?


感谢到目前为止所有的答案。

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

【问题讨论】:

  • 从扩展的角度来看,您必须使用次要-主要标记技术,否则当大量主要标记出现时性能会下降(它们会这样做)。特别是如果您使用非规范化模式,这会发生得更快 - 对于 RDBMS 解决方案总是

标签: sql database-design tags tagging


【解决方案1】:

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

ps - 大家在这里谈论的方法在文章中被称为“Toxi”解决方案。

【讨论】:

    【解决方案2】:

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

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

    连同性能测试:

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

    请注意,这里的结论是针对 MySQL 的,它(至少在 2005 年编写时)具有非常差的全文索引特性。

    【讨论】:

    【解决方案3】:

    最简单的方法是创建一个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]
    

    更新
    根据您对 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]
    

    【讨论】:

      【解决方案4】:

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

      【讨论】:

      • 使用这种方法,您只能搜索具有完全相同标签集的条目 - 这总是微不足道的。在我最初的问题中,我想找到包含我查询的所有标签的条目,可能还有更多。
      【解决方案5】:

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

      这种具有适当缓存机制的解决方案可能会产生比本土解决方案更好的性能。

      但是,我真的不认为在中小型应用程序中,您需要比前面文章中提到的规范化数据库更复杂的实现。

      编辑:根据您的说明,将类似 JCR 的解决方案与搜索引擎一起使用似乎更有吸引力。从长远来看,这将大大简化您的程序。

      【讨论】:

        【解决方案6】:

        关于 ANDing:听起来您正在寻找“关系除法”操作。 This article 以简明易懂的方式涵盖了关系划分。

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

        【讨论】:

        • 我不得不说这个答案有点短视,因为使用数据库的位字段类型会将您限制为特定数量的位。这并不意味着每个项目都被限制为一定数量的标签,而是整个系统中只能有一定数量的唯一标签(通常最多 32 或 64 个)。
        • 假设 3nf 实现(Question、Tag、Question_has_Tag)和 Question_has_Tag 中 Tag_id 上的位图索引,每次添加或删除问题时都必须重建位图索引。像select * from question q inner join question_has_tag qt where tag_id in (select tag_id from tags where (what we want) minus select tag_id from tags where (what we don't) 这样的查询应该没问题,并且假设中间表上存在正确的 b 树索引
        • “这篇文章”链接已失效。我本来想读那个:(
        • 马克:这个看起来不错:simple-talk.com/sql/t-sql-programming/… 这可能是我提到的那个的重新发布版本。
        • 文章的网址不再有效
        【解决方案7】:

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

        不知何故,我相信使用普通的 nvarchar 字段来存储带有适当缓存/索引的标签可能会产生更快的结果。但这只是我。

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

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

        【讨论】:

          【解决方案8】:

          套用其他人所说的话:诀窍不在于 schema,而在于 query

          实体/标签/标签的幼稚模式是正确的方法。但正如您所见,目前还不清楚如何使用大量标签执行 AND 查询。

          优化该查询的最佳方式将取决于平台,因此我建议使用您的 RDBS 重新标记您的问题,并将标题更改为“在标记数据库上执行 AND 查询的最佳方式”。

          我对 MS SQL 有一些建议,但如果这不是您使用的平台,我会避免。

          【讨论】:

          • 您可能不应该避免提供有关某项技术的花絮,因为尝试在该问题领域工作的其他人实际上可能正在使用该技术并且会受益。
          【解决方案9】:

          我认为直接解决方案没有问题:项目表、标签表、“标记”交叉表

          交叉表上的索引应该是足够的优化。选择合适的项目将是

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

          【讨论】:

            【解决方案10】:

            我喜欢做的是有一些代表原始数据的表格,所以在这种情况下你会有

            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 语句中使用嵌套查询,而是希望传入一个包含要从数据库中选择的标签列表的参数,并返回 Items 的记录集。这最好是二进制格式,使用位运算符。

            二进制格式,很容易解释。假设有四个标签要分配给一个项目,在二进制中我们可以表示

            0000
            

            如果将所有四个标签都分配给一个对象,则该对象将如下所示...

            1111
            

            如果只是前两个...

            1100
            

            那么这只是在您想要的列中找到具有 1 和 0 的二进制值的情况。使用 SQL Server 的位运算符,您可以使用非常简单的查询来检查第一列中是否有 1。

            查看此链接了解more

            【讨论】:

              【解决方案11】:

              您将无法避免连接,但仍会有些标准化。

              我的方法是有一个标签表。

               TagId (PK)| TagName (Indexed)
              

              然后,您的 items 表中有一个 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 等...并动态构建查询。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2018-05-02
                • 1970-01-01
                • 2011-09-10
                • 2010-09-22
                • 2018-11-20
                • 2012-07-23
                相关资源
                最近更新 更多