计算用户帖子

I am listing the tags/categories on the user's page. I would like to show the number of posts the user made for each tag. The tags, the posts, and the post-tags are in different tables. The difficulty is, that there are two kind of posts. The posts, and the comments. They are in the same table, but different type. "question" and "answer". the related_id at the answers are the id of the posts they are related to.

I tried to solve in pretty lot of way but couldn't get it to work.

My db structures: For tags:

tagid      tag_name

For posts

id         type(enum:"question","answer")      related_id      user_id

For post-tags:

post_id    tag_id

The code what I tried is the following:

$user_active_query = mysql_query("select    p.id,
                                            p.user_id,
                                            pt.post_id,
                                            count(pt.post_id),
                                            pt.tag_id,
                                            t.tagid,
                                            t.tag_name
                                            from posts p
                                            inner join post_tags pt
                                            inner join tags t
                                            on p.id = pt.post_id
                                            and pt.tag_id = t.tagid
                                            where p.user_id = '$uid'
                                            group by t.tagid");
while($useractive = mysql_fetch_array($user_active_query)) {
    $user_active_counter = $useractive['count(pt.post_id)'];    
    echo "<a href='' class='btn btn-mini' style='margin:3px;'>".$useractive['tag_name']." (".$user_active_counter.")</a>";
}

User id is given on the page. "$uid". I am just tired of the lot of try and asking for correction. First it seemed to be the best way to store the post-tags but now this is a nightmare. I mean, for me, its seems impossible to do this with this structure.

You can get both counts i.e the no of answers and no of questions posted by a user ,here is the trick also use proper join syntax you are missing the on clause for join

SELECT 
  p.id,
  p.user_id,
  pt.post_id,
  COUNT(pt.post_id) all_posts,
  COALESCE(SUM(`type` = 'question')) questions,
  COALESCE(SUM(`type` = 'answer')) answers,
  pt.tag_id,
  t.tagid,
  t.tag_name 
FROM tags t 
  LEFT JOIN post_tags pt  ON(pt.tag_id = t.tagid)
  LEFT JOIN posts p   ON p.id = pt.post_id 
WHERE p.user_id = '$uid' 
GROUP BY t.tagid 

Note in mysql sum with some expression will result in a boolean

Edit from comments add another condition using OR in your last join so first condition will join the posts that are associated with tags ,and as your explanation tags are not directly linked with answers but answer are linked to their question with related id so can join the related id of each answer to tag id so this way can get the tags for answers too

SELECT 
  p.id,
  p.user_id,
  pt.post_id,
  COUNT(pt.post_id) all_posts,
  COALESCE(SUM(`type` = 'question')) questions,
  COALESCE(SUM(`type` = 'answer')) answers,
  pt.tag_id,
  t.tagid,
  t.tag_name 
FROM tags t 
  LEFT JOIN post_tags pt  ON(pt.tag_id = t.tagid)
  LEFT JOIN posts p   ON (p.id = pt.post_id OR p.related_id = pt.post_id)
WHERE p.user_id = '$uid' 
GROUP BY t.tagid 

I think you only need to include p.type in your group by clause

$user_active_query = mysql_query("
        select 
        p.id,
        p.user_id,
        pt.post_id,
        count(pt.post_id),
        pt.tag_id,
        t.tagid,
        t.tag_name
        from posts p
        inner join post_tags pt
        inner join tags t
        on p.id = pt.post_id
        and pt.tag_id = t.tagid
        where p.user_id = '$uid'
        group by t.tagid, p.type"
    );

so, we will group per type too.

I'm not sure I fully understand your schema design. But it sounds like you have two join "paths", one to get to the question type posts, and another to get to child answer type posts.

To get the count of the question-type posts (by a specific user) related to each tag, looks like what you have so far, basically:

SELECT t.tagid
     , t.tag_name
     , COUNT(p.id) AS count_question
  FROM tags t
  JOIN post_tags pt
    ON pt.tag_id = t.tagid
  JOIN posts p
    ON p.id = pt.post_id
   AND p.type = 'question'
 WHERE p.user_id = '$uid'
 GROUP BY t.tagid

To get the count of the answer-type posts (by a specific user) related to a question related to each tag, we first need to join to the 'question' (to get the tags), and then join to the related answer. This is nearly identical to the first query, except that we add another join to posts table to get the "child" answer-type posts (so we can get a count of the answer-type posts), and we are not restricting the question-type posts to those from the specific user... we are going to count the answer-type posts from a user posted against any user's question.

SELECT t.tagid
     , t.tag_name
     , COUNT(a.id) AS count_answer
  FROM tags t
  JOIN post_tags pt
    ON pt.tag_id = t.tagid
  JOIN posts p
    ON p.id = pt.post_id
   AND p.type = 'question' 
  JOIN posts a
    ON a.related_id = p.id
   AND a.type = 'answer'
 WHERE a.user_id = '$uid'
 GROUP BY t.tagid

If each of those queries returns a portion of the total count you want to return, those can be combined, but it gets a little bit messy.

The most straightforward approach is to combine those two results with a UNION ALL set operator, and then use that as an inline view, i.e. run another query against the combined resultset.

For example:

SELECT r.tagid
     , r.tagname
     , SUM(r.count_post)                  AS count_total
     , SUM(IF(r.type='q',r.count_post,0)) AS count_question
     , SUM(IF(r.type='a',r.count_post,0)) AS count_answer
  FROM (
         SELECT 'q' AS type 
              , t.tagid
              , t.tag_name
              , COUNT(p.id) AS count_post
           FROM tags t
           JOIN post_tags pt
             ON pt.tag_id = t.tagid
           JOIN posts p
             ON p.id = pt.post_id
            AND p.type = 'question'
          WHERE p.user_id = '$uid'
          GROUP BY t.tagid

          UNION ALL

         SELECT 'a' AS type
              , t.tagid
              , t.tag_name
              , COUNT(a.id) AS count_post
           FROM tags t
           JOIN post_tags pt
             ON pt.tag_id = t.tagid
           JOIN posts p
             ON p.id = pt.post_id
            AND p.type = 'question' 
           JOIN posts a
             ON a.related_id = p.id
            AND a.type = 'answer'
           WHERE a.user_id = '$uid'
          GROUP BY t.tagid
       ) r
   GROUP BY r.tag_id

If you aren't interested in the individual counts (of question and answer type posts), then just remove those two expressions from the SELECT list of the outer query, and you can also remove the type='q', type='a' discriminator column from the inline view query.

This isn't the only way to combine the results, but I think it's the easiest way to verify we're getting a "correct" result (we can run just the inline view query and verify that the results from that are correct.


Another approach to combining them is messier, and more difficult to decipher.

We basically need to join to question-type posts from all users, and then do an outer join operation to the answer-type posts from the specific user.

We can use predicates in the WHERE clause to filter out the rows, so that return only rows that have a matching answer-type row -OR- are a question-type row poseted by the specified user.

In the SELECT list, we need to do some additional filtering, so that we filter out posts from other users.

Something like this:

   SELECT t.tagid
        , t.tag_name
        , COUNT(DISTINCT IF(p.user_id='$uid',p.id,NULL))
          + COUNT(DISTINCT a.id)                         AS count_total
        , COUNT(DISTINCT IF(p.user_id='$uid',p.id,NULL)) AS count_question
        , COUNT(DISTINCT a.id)                           AS count_question
     FROM tags t
     JOIN post_tags pt
       ON pt.tag_id = t.tagid
     JOIN posts p
       ON p.id = pt.post_id
      AND p.type = 'question'
     LEFT
     JOIN posts a
       ON a.related_id = p.id
      AND a.type = 'answer'
      AND a.user_id = '$uid'
    WHERE p.user_id = '$uid'
       OR a.id IS NOT NULL
    GROUP BY t.tagid

But, I'd don't really like this query, it's too hard to figure out what's going on. I'd opt for the (previous) query, with the UNION ALL inline view. That's easier to decipher.