【问题标题】:How can I implement an "interesting tags" feature like that on Stack Overflow?如何在 Stack Overflow 上实现类似“有趣的标签”功能?
【发布时间】:2011-02-24 07:07:35
【问题描述】:

用赏金检查我的另一个问题: Finding similar number patterns in table

我正在尝试实现一个有趣的标签功能。作为参考,这是它在 SO 上的工作方式:

  1. 我将我感兴趣的标签(如 php、mysql、jquery 等)添加到“有趣”列表中。
  2. 然后,如果任何显示的问题在我的列表中有一些标签,它会使背景变为橙色。

我了解如何使用 jQuery 来做到这一点(有相关的问题),但无法弄清楚如何使用 MySQL 实现后端部分!

所以这是我的问题:它是如何完成的?我想它是这样工作的:

  • mysql 中每个成员都有一行,我们称之为“interested_tags”。
  • 在我通过输入编写并提交我的标签后,它被写在一行“interested_tags”中。
  • 然后,主页有一个显示所有答案的查询,它总是使用 strpos 来检查问题的标签和我的标签,如下所示:

    if(strpos($question_tags, $my_tags) === true) {
       //and here will be made background orange
    }
    

我的想法是对的还是有什么办法?

编辑:那么,您能给我举个例子或给我一些提示,如何通过多对多关系实现这一点吗?谢谢。

【问题讨论】:

  • 尽管有标题,但这不是元问题。提问者不是问什么做了什么,而是问怎么做,这显然是一个编程问题。
  • 哇 Shoq,非常好的编辑,谢谢。
  • 我的回答没有涵盖您问题的哪一部分?
  • 你可以这样做(所有有趣的标签在一列中,而不是像 Simen 所示的单独的多对多表),但有一些缺点:1)你必须进行解析和过滤自己,而不是让数据库来做这项工作,2)字段的长度限制可能更不方便处理,3)询问“哪些用户认为标签 YYY 有趣?”太贵了。不过,优点是通过用户 ID 查找整个集合非常快,因为它只是单列查找,而不是表连接。

标签: php jquery mysql tags


【解决方案1】:

正如其他答案中提到的,用户和标签之间很可能存在多对多关系,表示为自己的表。我做了一个简化案例的 SQL 演示。 InterestingTags表是连接用户对哪些标签感兴趣的表。

/* Create tables */
CREATE TABLE User (id INT NOT NULL AUTO_INCREMENT, name varchar(50), PRIMARY KEY(id));
CREATE TABLE Tag (id INT NOT NULL AUTO_INCREMENT, name varchar(50), PRIMARY KEY(id));
CREATE TABLE InterestingTags (user_id INT NOT NULL REFERENCES User(id), tag_id INT NOT NULL REFERENCES Tag(id), PRIMARY KEY(user_id,tag_id));

/* Insert some data */
/* 3 users, 5 tags and some connections between users and tags */
INSERT INTO User (name) VALUES ('jQueryFreak'), ('noFavoriteMan'), ('generalist'); 
INSERT INTO Tag (name) VALUES ('jQuery'), ('php'), ('asp.net'), ('c#'), ('ruby');
INSERT INTO InterestingTags (user_id, tag_id) VALUES (1,1), (3,1), (3,2), (3,3), (3,4);

/* Select all the users and what tags they are interested in */
SELECT u.name, t.name FROM User u 
LEFT JOIN InterestingTags it ON it.user_id = u.id 
LEFT JOIN Tag t ON t.id = it.tag_id;

/* Select all tag ids that are interesting to user 3 ("generalist") */
SELECT tag_id FROM InterestingTags WHERE user_id = 3;

/* 
    Now let's introduce a questions table.
    For simplicity, let's say a question can only have one tag. 
    There's really a many-to-many relationship here, too, as with user and tag
*/
CREATE TABLE Question (id INT NOT NULL AUTO_INCREMENT, title VARCHAR(50) NOT NULL, tag_id INT NOT NULL REFERENCES Tag(id), PRIMARY KEY(id));

/* Insert some questions */
INSERT INTO Question (title, tag_id) VALUES 
    ('generating random numbers in php', 2),     /*php question*/
    ('hiding divs in jQuery', 1),                /*jQuery question*/
    ('how do i add numbers with jQuery', 1),     /*jQuery question 2*/
    ('asp.net help', 3),                         /*asp.net question */
    ('c# question', 4),                          /*c# question */
    ('ruby question', 5);                        /*ruby question */

/* select all questions and what users are interested in them */
SELECT q.title, u.name FROM Question q
LEFT JOIN InterestingTags it ON it.tag_id = q.tag_id 
LEFT JOIN User u ON u.id = it.user_id;


/* select all questions a user will be interested in. Here the user is jQueryFreak with id = 1 */
SELECT q.id, q.title FROM Question q
LEFT JOIN InterestingTags it ON it.tag_id = q.tag_id
LEFT JOIN User u ON u.id = it.user_id
WHERE u.id = 1;


/* Select all questions and indicate whether or not jQueryFreak (with id = 1) is interested in each one */
/* TODO: make SO question about how to do this as efficient as possible :) */
SELECT q.id, q.title,
    (SELECT COUNT(*) FROM InterestingTags it 
    WHERE it.tag_id = q.tag_id AND it.user_id = 1)
    AS is_interested 
FROM Question q;


/* Let's add a many-to-many relationship between questions and tags. 
   Questions can now have many tags 
*/
ALTER TABLE Question DROP COLUMN tag_id;

CREATE TABLE Question_Tag ( 
    question_id INT NOT NULL REFERENCES Question (id),
    tag_id      INT NOT NULL REFERENCES Tag (id),
    PRIMARY KEY (question_id, tag_id)
);

/* Insert relationships between questions and tags */
INSERT INTO Question_Tag VALUES
    /* First the tags as in the above examples */
    (1,2), (2,1), (3,1),(4,3),(5,4),(6,5),
    /* And some more. ASP.NET question is also tagged C#
    and php question is tagged jQuery */
    (1,1), (4,4);


/* select all questions and what users are interested in them
(Some combinations will show up multiple times. This duplication is removed in the 
two following queries but I didn't find a solution for it here)*/
SELECT q.title, u.name FROM Question q
LEFT JOIN Question_Tag qt ON qt.question_id = q.id /* <-- new join */
LEFT JOIN InterestingTags it ON it.tag_id = qt.tag_id 
LEFT JOIN User u ON u.id = it.user_id;


/* select all questions a user will be interested in. Here the user is jQueryFreak with id = 1 */
SELECT q.id, q.title FROM Question q
LEFT JOIN Question_Tag qt ON qt.question_id = q.id /* <-- new join */
LEFT JOIN InterestingTags it ON it.tag_id = qt.tag_id
LEFT JOIN User u ON u.id = it.user_id
WHERE u.id = 1
GROUP BY q.id; /* prevent duplication of a question in the result list */


/* Select all questions and indicate whether or not jQueryFreak (with id = 1) is interested in each one */
/* STILL TODO: make SO question about how to do this as efficient as possible :) */
SELECT q.id, q.title,
    (SELECT COUNT(*) FROM InterestingTags it
     WHERE it.tag_id = qt.tag_id AND it.user_id = 1)
    AS is_interested 
FROM Question q
LEFT JOIN Question_Tag qt ON qt.question_id = q.id /* <-- new join */
GROUP BY q.id;


更新:添加了 php 演示
记得在运行演示之前更改你的 mysql 常量

这样做是对数据库运行两个查询:

  • 一个询问所有问题及其标签的人
  • 询问用户感兴趣的标签。

要使用标签“标记”一个问题,它会为它所属的每个标签添加一个class——例如用jQuery(其中jQuery 的ID 为1)和php(ID 为2)标记的问题将具有tagged-1tagged-2 的类。

现在将此与其他查询结合起来,获取有趣的标签,您只需选择具有与有趣标签对应的类的问题并设置它们的样式。例如,如果您对 ID 为 13 的标签感兴趣,则可以使用以下 jQuery 代码 $('.tagged-1, .tagged-3').addClass('interesting-tag');

<?php
const mysql_host = "localhost";
const mysql_username = "";
const mysql_password = "";
const mysql_database = "INTERESTINGTEST";

const user_id = 1; //what user is viewing the page?

class Question {
    public $id;
    public $title;
    public $tags;

    function __construct($id,$title) {
        $this->id = $id;
        $this->title = $title;
        $this->tags = array();
    }
}

class Tag {
    public $id;
    public $name;

    function __construct($id,$name) {
        $this->id = $id;
        $this->name = $name;
    }
}

/**************************
Getting info from database
****************************/
mysql_connect(mysql_host,mysql_username,mysql_password);
mysql_select_db(mysql_database);


//Fetch interesting tags
$result = mysql_query("SELECT tag_id FROM InterestingTags WHERE user_id = " . user_id);
$interesting_tags = array();
while($row = mysql_fetch_array($result))
{
    $interesting_tags[] = $row['tag_id'];
}


//Fetch all questions and their tags
$query_select_questions =
'SELECT q.id AS q_id, q.title AS q_title, t.id AS t_id, t.name AS t_name FROM Question q
LEFT JOIN Question_Tag qt ON qt.question_id = q.id
LEFT JOIN Tag t ON t.id = qt.tag_id';

$result = mysql_query($query_select_questions);
$questions = array();

while($row = mysql_fetch_array($result))
{
    $q_id =    $row['q_id'];
    $q_title = $row['q_title'];
    $t_id =    $row['t_id'];
    $t_name =  $row['t_name'];

    if (!array_key_exists($q_id, $questions))
        $questions[$q_id] = new Question($q_id, $q_title);

    $questions[$q_id]->tags[] = new Tag($t_id, $t_name);
}

mysql_close();


/**************************
Write document
****************************/
?>

<style>
    .question { padding:0px 5px 5px 5px; border:1px solid gray; margin-bottom: 10px; width:400px }
    .interesting-tag { background-color: #FFEFC6 }
</style>

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.js"></script>

<script>
    var interesting_tags = [ <?php echo implode($interesting_tags,',') ?> ];
    var tagclass_prefix = ".tagged-";
    var tags_selector = tagclass_prefix + interesting_tags.join(", " + tagclass_prefix);

    $(function() {
        $(tags_selector).addClass("interesting-tag");
    });
</script>


<?php
    foreach ($questions as $q) {
        $tagsIDs = array();
        $tagNames = array();
        foreach ($q->tags as $tag) {
            $tagsIDs[] = $tag->id;
            $tagNames[] = $tag->name;
        }
        $classValue = "tagged-" . implode($tagsIDs," tagged-");
        $tagNames = implode($tagNames, ", ");
?>

<div id="question-<?php echo $q->id ?>" class="question <?php echo $classValue ?>">
    <h3><?php echo $q->title ?></h3>
    Tagged with <strong><?php echo $tagNames ?></strong>
</div>

<?php
    }
?>

【讨论】:

  • 也为 Question 和 Tag 关系创建一个表。
【解决方案2】:

mysql 中有一行 成员,让我们称之为 “interested_tags”。

更有可能的是,有一个额外的表表示用户和标签之间的多对多关系。使用另一个将标签与问题相关联的表格。

那么您只需要一个查询(或者更可能是一个存储过程),它将用户的标签与问题的标签进行比较,并返回一个布尔值 true 或 false。

【讨论】:

    【解决方案3】:

    堆栈溢出标签至少与标签中的 * 一起工作,因此将标签存储在数组中并使用模式匹配遍历它们(无论您使用 glob、SQL 还是正则表达式,只要用户知道将使用哪个)。

    【讨论】:

      【解决方案4】:

      这可能会启发你。 正如凯利所说,它是在页面加载后用 Javascript 完成的。据我所知,它们会加载所有问题的所有标签,并且那些与右侧具有相同标签的标签会被突出显示。见

      【讨论】:

        【解决方案5】:

        @new 问题:http://skmzq.qiniucdn.com/data/20060423142114/index.html 包含不同类型标记的(旧但有用的)比较。不要忘记阅读 cmets,它们非常有用。

        【讨论】:

          【解决方案6】:

          从页面呈现的方式来看,我猜测标签比较是在 JavaScript 中完成的。所以步骤是:

          • 查询用户感兴趣的标签
          • 将结果提供给客户端 JS
          • JS 遍历每个问题并根据匹配更改属性(包括如果帖子匹配“忽略”标签,则删除该帖子。

          【讨论】: