【问题标题】:Algorithm that searches for related items based on common tags基于公共标签搜索相关项目的算法
【发布时间】:2010-12-06 02:18:56
【问题描述】:

让我们以 StackOverflow 问题为例。他们每个人都分配了多个标签。如何构建一个算法,根据他们有多少常见标签(按常见标签数量排序)找到相关问题?

现在我想不出比将所有至少有一个公共标签的问题都选择到一个数组中,然后循环遍历它们,为每个项目分配多个公共标签,然后对这个数组进行排序更好的方法。

还有更聪明的方法吗?完美的解决方案是单个 sql 查询。

【问题讨论】:

  • 如果您正在寻找的解决方案是 SQL 查询,那么了解您用于存储标签的架构会很有帮助。
  • @Emily:只是与中间表的常规多对多关系。

标签: mysql algorithm search tags many-to-many


【解决方案1】:
select *
     , count(t.*) as matched_tag_count
  from questions q
  join tags_to_questions t
    on t.question_id = q.id
   and t.tag_id in (select tag_id
                      from tags_to_questions
                     where question_id = ?)
group
    by q.id
order
    by matched_tag_count desc

【讨论】:

    【解决方案2】:

    假设一个表 Questions 具有一个主键列 Id,一个表 Tags 也具有一个主键列 Id,以及一个连接表 QuestionTags 具有一个复合主键 QuestionIdTagId 引用前两个表的主键,以下查询将给出所需的结果(在 SQL Server 2005 中)。

    SELECT q1.Id AS Id1, q2.Id AS Id2,
       (SELECT COUNT(*) FROM QuestionTags qt1 INNER JOIN QuestionTags qt2
        ON qt1.QuestionId = q1.Id AND qt2.QuestionId = q2.Id AND qt1.TagId = qt2.TagId) AS TagCount
    FROM Questions q1 INNER JOIN Questions q2 ON q1.Id < q2.Id
    ORDER BY TagCount DESC
    

    这可以改进为以下。

    SELECT qt1.QuestionId AS Id1, qt2.QuestionId AS Id2, COUNT(*) AS TagCount
    FROM QuestionTags qt1 INNER JOIN QuestionTags qt2
    ON qt1.QuestionId < qt2.QuestionId AND qt1.TagId = qt2.TagId
    GROUP BY qt1.QuestionId, qt2.QuestionId
    ORDER BY TagCount DESC
    

    【讨论】:

      【解决方案3】:

      假设我有以下内容:

      可标记实体表:

      Taggable

      id, stuff

      标签表:

      Tag

      id, tagName

      哪些标签与哪些Taggables相关联的连接表

      TagInfo

      taggableId tagId

      然后:

      SELECT COUNT(Taggable_1.id) AS score, Taggable_1.id FROM dbo.Taggable INNER JOIN dbo.TagInfo ON dbo.Taggable.id = dbo.TagInfo.taggableId INNER JOIN dbo.TagInfo TagInfo_1 ON dbo.TagInfo.tagId = TagInfo_1.tagId INNER JOIN dbo.Taggable Taggable_1 ON TagInfo_1.taggableId = Taggable_1.id WHERE (dbo.Taggable.id = 1) AND (Taggable_1.id &lt;&gt; 1) GROUP BY Taggable_1.id

      将标签连接表与自身连接(对于查询的问题 id;在上面的 sql 中为 1)并计算结果以获得分数。

      【讨论】:

        【解决方案4】:

        我没有时间优化 WHERE IN() 子句,它慢得像死一样。我也没有同时包含索引或指定引擎或排序规则,但这应该可以解决问题。请指出任何错误,因为我没有实际测试过这个草率的代码:

        CREATE TABLE question (
            id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
            title VARCHAR(255) NOT NULL,
            question MEDIUMTEXT
        );
        
        CREATE TABLE question_rel_tag (
            id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
            question_id INT(11) UNSIGNED NOT NULL,
            tag_id INT(11) UNSIGNED NOT NULL
        );
        
        CREATE TABLE tag (
            id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
            name VARCHAR(20) NOT NULL
        );
        
        SELECT question.id, question.title, question.question, count(tag.id) AS `count`
        FROM question
        INNER JOIN question_rel_tag ON (question.id = question_rel_tag.question_id)
        INNER JOIN tag ON (question_rel_tag.tag_id = tag.id)
        WHERE question.id != YOUR_QUESTION_ID_HERE
        AND tag.id IN 
            (SELECT tag.id FROM question
            INNER JOIN question_rel_tag ON (question.id = question_rel_tag.question_id)
            INNER JOIN tag ON (question_rel_tag.tag_id = tag.id)
            WHERE question.id = YOUR_QUESTION_ID_HERE)
        GROUP BY tag.id
        ORDER BY `count` DESC
        

        【讨论】:

        • 您也许可以使用WHERE ... tag.id EXISTS (... 而不是较慢的IN (...)
        【解决方案5】:

        这可能和 O(n^2) 一样糟糕,但它确实有效:

        create table QuestionTags (questionid int, tag int);
        
        select q1.questionid, q2.questionid, count(*) as commontags
        from QuestionTags q1 join QuestionTags q2 
        where q1.tag = q2.tag and q1.questionid < q2.questionid
        group by q1.questionid, q2.questionid order by commontags desc;
        

        【讨论】:

        • 谢谢你,我通过阅读这个优雅的例子学到了一些关于 SQL 的知识。
        • 你的意思是q1.questionid != q2.questionid
        • @Xeoncross:不,我的意思是&lt;。它既避免了自匹配(!= 也避免了),也避免了将每对列出两次(!= 没有)。
        • 是的,我运行后看到了。使用简单的!=,每对都列出了两次。谢谢。
        猜你喜欢
        • 1970-01-01
        • 2020-08-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多